mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
input: begin kitty key encoding logic (not working yet)
This commit is contained in:
@ -10,6 +10,10 @@ const testing = std.testing;
|
|||||||
|
|
||||||
const key = @import("key.zig");
|
const key = @import("key.zig");
|
||||||
const function_keys = @import("function_keys.zig");
|
const function_keys = @import("function_keys.zig");
|
||||||
|
const terminal = @import("../terminal/main.zig");
|
||||||
|
const KittyEntry = @import("kitty.zig").Entry;
|
||||||
|
const kitty_entries = @import("kitty.zig").entries;
|
||||||
|
const KittyFlags = terminal.kitty.KeyFlags;
|
||||||
|
|
||||||
event: key.KeyEvent,
|
event: key.KeyEvent,
|
||||||
|
|
||||||
@ -18,6 +22,66 @@ alt_esc_prefix: bool = false,
|
|||||||
cursor_key_application: bool = false,
|
cursor_key_application: bool = false,
|
||||||
keypad_key_application: bool = false,
|
keypad_key_application: bool = false,
|
||||||
modify_other_keys_state_2: bool = false,
|
modify_other_keys_state_2: bool = false,
|
||||||
|
kitty_flags: KittyFlags = .{},
|
||||||
|
|
||||||
|
/// Perform Kitty keyboard protocol encoding of the key event.
|
||||||
|
pub fn kitty(
|
||||||
|
self: *const KeyEncoder,
|
||||||
|
buf: []u8,
|
||||||
|
) ![]const u8 {
|
||||||
|
// This should never happen but we'll check anyway.
|
||||||
|
if (self.kitty_flags.int() == 0) return try self.legacy(buf);
|
||||||
|
|
||||||
|
// We only processed "press" events unless report events is active
|
||||||
|
if (self.event.action != .press and !self.kitty_flags.report_events)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
const all_mods = self.event.mods;
|
||||||
|
const effective_mods = self.event.effectiveMods();
|
||||||
|
const binding_mods = effective_mods.binding();
|
||||||
|
|
||||||
|
// Find the entry for this key in the kitty table.
|
||||||
|
const entry: ?KittyEntry = entry: {
|
||||||
|
for (kitty_entries) |entry| {
|
||||||
|
if (entry.key == self.event.key) break :entry entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
break :entry null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Quote:
|
||||||
|
// The only exceptions are the Enter, Tab and Backspace keys which
|
||||||
|
// still generate the same bytes as in legacy mode this is to allow the
|
||||||
|
// user to type and execute commands in the shell such as reset after a
|
||||||
|
// program that sets this mode crashes without clearing it.
|
||||||
|
if (effective_mods.empty()) {
|
||||||
|
switch (self.event.key) {
|
||||||
|
.enter => return try copyToBuf(buf, "\r"),
|
||||||
|
.tab => return try copyToBuf(buf, "\t"),
|
||||||
|
.backspace => return try copyToBuf(buf, "\x7F"),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send plain-text non-modified text directly to the terminal.
|
||||||
|
// We don't send release events because those are specially encoded.
|
||||||
|
if (self.event.utf8.len > 0 and
|
||||||
|
binding_mods.empty() and
|
||||||
|
self.event.action != .release)
|
||||||
|
{
|
||||||
|
return try copyToBuf(buf, self.event.utf8);
|
||||||
|
}
|
||||||
|
|
||||||
|
const kitty_mods = KittyMods.fromInput(all_mods);
|
||||||
|
const final_entry = entry orelse {
|
||||||
|
// TODO: we need to look it up
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
_ = kitty_mods;
|
||||||
|
_ = final_entry;
|
||||||
|
|
||||||
|
return "X";
|
||||||
|
}
|
||||||
|
|
||||||
/// Perform legacy encoding of the key event. "Legacy" in this case
|
/// Perform legacy encoding of the key event. "Legacy" in this case
|
||||||
/// is referring to the behavior of traditional terminals, plus
|
/// is referring to the behavior of traditional terminals, plus
|
||||||
@ -312,7 +376,6 @@ const CsiUMods = packed struct(u3) {
|
|||||||
const raw: u4 = @intCast(self.int());
|
const raw: u4 = @intCast(self.int());
|
||||||
return raw + 1;
|
return raw + 1;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
test "modifer sequence values" {
|
test "modifer sequence values" {
|
||||||
// This is all sort of trivially seen by looking at the code but
|
// This is all sort of trivially seen by looking at the code but
|
||||||
@ -341,6 +404,123 @@ test "modifer sequence values" {
|
|||||||
mods = .{ .alt = true, .ctrl = true, .shift = true };
|
mods = .{ .alt = true, .ctrl = true, .shift = true };
|
||||||
try testing.expectEqual(@as(u4, 8), mods.seqInt());
|
try testing.expectEqual(@as(u4, 8), mods.seqInt());
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This is the bitfields for Kitty modifiers.
|
||||||
|
const KittyMods = packed struct(u8) {
|
||||||
|
shift: bool = false,
|
||||||
|
alt: bool = false,
|
||||||
|
ctrl: bool = false,
|
||||||
|
super: bool = false,
|
||||||
|
hyper: bool = false,
|
||||||
|
meta: bool = false,
|
||||||
|
caps_lock: bool = false,
|
||||||
|
num_lock: bool = false,
|
||||||
|
|
||||||
|
/// Convert an input mods value into the CSI u mods value.
|
||||||
|
pub fn fromInput(mods: key.Mods) KittyMods {
|
||||||
|
return .{
|
||||||
|
.shift = mods.shift,
|
||||||
|
.alt = mods.alt,
|
||||||
|
.ctrl = mods.ctrl,
|
||||||
|
.super = mods.super,
|
||||||
|
.caps_lock = mods.caps_lock,
|
||||||
|
.num_lock = mods.num_lock,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the raw int value of this packed struct.
|
||||||
|
pub fn int(self: KittyMods) u8 {
|
||||||
|
return @bitCast(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the integer value sent as part of the Kitty sequence.
|
||||||
|
/// This adds 1 to the bitmask value as described in the spec.
|
||||||
|
pub fn seqInt(self: KittyMods) u9 {
|
||||||
|
const raw: u9 = @intCast(self.int());
|
||||||
|
return raw + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "modifer sequence values" {
|
||||||
|
// This is all sort of trivially seen by looking at the code but
|
||||||
|
// we want to make sure we never regress this.
|
||||||
|
var mods: KittyMods = .{};
|
||||||
|
try testing.expectEqual(@as(u9, 1), mods.seqInt());
|
||||||
|
|
||||||
|
mods = .{ .shift = true };
|
||||||
|
try testing.expectEqual(@as(u9, 2), mods.seqInt());
|
||||||
|
|
||||||
|
mods = .{ .alt = true };
|
||||||
|
try testing.expectEqual(@as(u9, 3), mods.seqInt());
|
||||||
|
|
||||||
|
mods = .{ .ctrl = true };
|
||||||
|
try testing.expectEqual(@as(u9, 5), mods.seqInt());
|
||||||
|
|
||||||
|
mods = .{ .alt = true, .shift = true };
|
||||||
|
try testing.expectEqual(@as(u9, 4), mods.seqInt());
|
||||||
|
|
||||||
|
mods = .{ .ctrl = true, .shift = true };
|
||||||
|
try testing.expectEqual(@as(u9, 6), mods.seqInt());
|
||||||
|
|
||||||
|
mods = .{ .alt = true, .ctrl = true };
|
||||||
|
try testing.expectEqual(@as(u9, 7), mods.seqInt());
|
||||||
|
|
||||||
|
mods = .{ .alt = true, .ctrl = true, .shift = true };
|
||||||
|
try testing.expectEqual(@as(u9, 8), mods.seqInt());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents a kitty key sequence and has helpers for encoding it.
|
||||||
|
/// The sequence from the Kitty specification:
|
||||||
|
///
|
||||||
|
/// CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
|
||||||
|
const KittySequence = struct {
|
||||||
|
key: u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
test "kitty: plain text" {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
var enc: KeyEncoder = .{
|
||||||
|
.event = .{
|
||||||
|
.key = .a,
|
||||||
|
.mods = .{},
|
||||||
|
.utf8 = "abcd",
|
||||||
|
},
|
||||||
|
|
||||||
|
.kitty_flags = .{ .disambiguate = true },
|
||||||
|
};
|
||||||
|
|
||||||
|
const actual = try enc.kitty(&buf);
|
||||||
|
try testing.expectEqualStrings("abcd", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "kitty: enter, backspace, tab" {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
{
|
||||||
|
var enc: KeyEncoder = .{
|
||||||
|
.event = .{ .key = .enter, .mods = .{}, .utf8 = "" },
|
||||||
|
.kitty_flags = .{ .disambiguate = true },
|
||||||
|
};
|
||||||
|
const actual = try enc.kitty(&buf);
|
||||||
|
try testing.expectEqualStrings("\r", actual);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var enc: KeyEncoder = .{
|
||||||
|
.event = .{ .key = .backspace, .mods = .{}, .utf8 = "" },
|
||||||
|
.kitty_flags = .{ .disambiguate = true },
|
||||||
|
};
|
||||||
|
const actual = try enc.kitty(&buf);
|
||||||
|
try testing.expectEqualStrings("\x7f", actual);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var enc: KeyEncoder = .{
|
||||||
|
.event = .{ .key = .tab, .mods = .{}, .utf8 = "" },
|
||||||
|
.kitty_flags = .{ .disambiguate = true },
|
||||||
|
};
|
||||||
|
const actual = try enc.kitty(&buf);
|
||||||
|
try testing.expectEqualStrings("\t", actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "legacy: ctrl+alt+c" {
|
test "legacy: ctrl+alt+c" {
|
||||||
var buf: [128]u8 = undefined;
|
var buf: [128]u8 = undefined;
|
||||||
|
Reference in New Issue
Block a user