mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
terminal: move kitty keyboard protocol to its own file
This commit is contained in:
@ -1,154 +1,3 @@
|
|||||||
//! Types and functions related to Kitty protocols.
|
//! Types and functions related to Kitty protocols.
|
||||||
//!
|
|
||||||
//! Documentation for the Kitty keyboard protocol:
|
|
||||||
//! https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
|
|
||||||
|
|
||||||
const std = @import("std");
|
pub usingnamespace @import("kitty/key.zig");
|
||||||
|
|
||||||
/// 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 KeyFlagStack = struct {
|
|
||||||
const len = 8;
|
|
||||||
|
|
||||||
flags: [len]KeyFlags = .{.{}} ** len,
|
|
||||||
idx: u3 = 0,
|
|
||||||
|
|
||||||
/// Return the current stack value
|
|
||||||
pub fn current(self: KeyFlagStack) KeyFlags {
|
|
||||||
return self.flags[self.idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Perform the "set" operation as described in the spec for
|
|
||||||
/// the CSI = u sequence.
|
|
||||||
pub fn set(
|
|
||||||
self: *KeyFlagStack,
|
|
||||||
mode: KeySetMode,
|
|
||||||
v: KeyFlags,
|
|
||||||
) 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: *KeyFlagStack, flags: KeyFlags) 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: *KeyFlagStack, 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 = .{.{}} ** len;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0..n) |_| {
|
|
||||||
self.flags[self.idx] = .{};
|
|
||||||
self.idx -%= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we the overflow works as expected
|
|
||||||
test {
|
|
||||||
const testing = std.testing;
|
|
||||||
var stack: KeyFlagStack = .{};
|
|
||||||
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 KeyFlags = packed struct(u5) {
|
|
||||||
disambiguate: bool = false,
|
|
||||||
report_events: bool = false,
|
|
||||||
report_alternates: bool = false,
|
|
||||||
report_all: bool = false,
|
|
||||||
report_associated: bool = false,
|
|
||||||
|
|
||||||
pub fn int(self: KeyFlags) 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),
|
|
||||||
(KeyFlags{ .disambiguate = true }).int(),
|
|
||||||
);
|
|
||||||
try testing.expectEqual(
|
|
||||||
@as(u5, 0b10),
|
|
||||||
(KeyFlags{ .report_events = true }).int(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The possible modes for setting the key flags.
|
|
||||||
pub const KeySetMode = enum { set, @"or", not };
|
|
||||||
|
|
||||||
test "KeyFlagStack: push pop" {
|
|
||||||
const testing = std.testing;
|
|
||||||
var stack: KeyFlagStack = .{};
|
|
||||||
stack.push(.{ .disambiguate = true });
|
|
||||||
try testing.expectEqual(
|
|
||||||
KeyFlags{ .disambiguate = true },
|
|
||||||
stack.current(),
|
|
||||||
);
|
|
||||||
|
|
||||||
stack.pop(1);
|
|
||||||
try testing.expectEqual(KeyFlags{}, stack.current());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "KeyFlagStack: pop big number" {
|
|
||||||
const testing = std.testing;
|
|
||||||
var stack: KeyFlagStack = .{};
|
|
||||||
stack.pop(100);
|
|
||||||
try testing.expectEqual(KeyFlags{}, stack.current());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "KeyFlagStack: set" {
|
|
||||||
const testing = std.testing;
|
|
||||||
var stack: KeyFlagStack = .{};
|
|
||||||
stack.set(.set, .{ .disambiguate = true });
|
|
||||||
try testing.expectEqual(
|
|
||||||
KeyFlags{ .disambiguate = true },
|
|
||||||
stack.current(),
|
|
||||||
);
|
|
||||||
|
|
||||||
stack.set(.@"or", .{ .report_events = true });
|
|
||||||
try testing.expectEqual(
|
|
||||||
KeyFlags{
|
|
||||||
.disambiguate = true,
|
|
||||||
.report_events = true,
|
|
||||||
},
|
|
||||||
stack.current(),
|
|
||||||
);
|
|
||||||
|
|
||||||
stack.set(.not, .{ .report_events = true });
|
|
||||||
try testing.expectEqual(
|
|
||||||
KeyFlags{ .disambiguate = true },
|
|
||||||
stack.current(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
151
src/terminal/kitty/key.zig
Normal file
151
src/terminal/kitty/key.zig
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
//! 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 KeyFlagStack = struct {
|
||||||
|
const len = 8;
|
||||||
|
|
||||||
|
flags: [len]KeyFlags = .{.{}} ** len,
|
||||||
|
idx: u3 = 0,
|
||||||
|
|
||||||
|
/// Return the current stack value
|
||||||
|
pub fn current(self: KeyFlagStack) KeyFlags {
|
||||||
|
return self.flags[self.idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform the "set" operation as described in the spec for
|
||||||
|
/// the CSI = u sequence.
|
||||||
|
pub fn set(
|
||||||
|
self: *KeyFlagStack,
|
||||||
|
mode: KeySetMode,
|
||||||
|
v: KeyFlags,
|
||||||
|
) 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: *KeyFlagStack, flags: KeyFlags) 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: *KeyFlagStack, 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 = .{.{}} ** len;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0..n) |_| {
|
||||||
|
self.flags[self.idx] = .{};
|
||||||
|
self.idx -%= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we the overflow works as expected
|
||||||
|
test {
|
||||||
|
const testing = std.testing;
|
||||||
|
var stack: KeyFlagStack = .{};
|
||||||
|
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 KeyFlags = packed struct(u5) {
|
||||||
|
disambiguate: bool = false,
|
||||||
|
report_events: bool = false,
|
||||||
|
report_alternates: bool = false,
|
||||||
|
report_all: bool = false,
|
||||||
|
report_associated: bool = false,
|
||||||
|
|
||||||
|
pub fn int(self: KeyFlags) 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),
|
||||||
|
(KeyFlags{ .disambiguate = true }).int(),
|
||||||
|
);
|
||||||
|
try testing.expectEqual(
|
||||||
|
@as(u5, 0b10),
|
||||||
|
(KeyFlags{ .report_events = true }).int(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The possible modes for setting the key flags.
|
||||||
|
pub const KeySetMode = enum { set, @"or", not };
|
||||||
|
|
||||||
|
test "KeyFlagStack: push pop" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var stack: KeyFlagStack = .{};
|
||||||
|
stack.push(.{ .disambiguate = true });
|
||||||
|
try testing.expectEqual(
|
||||||
|
KeyFlags{ .disambiguate = true },
|
||||||
|
stack.current(),
|
||||||
|
);
|
||||||
|
|
||||||
|
stack.pop(1);
|
||||||
|
try testing.expectEqual(KeyFlags{}, stack.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "KeyFlagStack: pop big number" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var stack: KeyFlagStack = .{};
|
||||||
|
stack.pop(100);
|
||||||
|
try testing.expectEqual(KeyFlags{}, stack.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "KeyFlagStack: set" {
|
||||||
|
const testing = std.testing;
|
||||||
|
var stack: KeyFlagStack = .{};
|
||||||
|
stack.set(.set, .{ .disambiguate = true });
|
||||||
|
try testing.expectEqual(
|
||||||
|
KeyFlags{ .disambiguate = true },
|
||||||
|
stack.current(),
|
||||||
|
);
|
||||||
|
|
||||||
|
stack.set(.@"or", .{ .report_events = true });
|
||||||
|
try testing.expectEqual(
|
||||||
|
KeyFlags{
|
||||||
|
.disambiguate = true,
|
||||||
|
.report_events = true,
|
||||||
|
},
|
||||||
|
stack.current(),
|
||||||
|
);
|
||||||
|
|
||||||
|
stack.set(.not, .{ .report_events = true });
|
||||||
|
try testing.expectEqual(
|
||||||
|
KeyFlags{ .disambiguate = true },
|
||||||
|
stack.current(),
|
||||||
|
);
|
||||||
|
}
|
Reference in New Issue
Block a user