input: don't emit fallback text on key release

Key events without a kitty entry fall back to writing their UTF-8 text
directly. On GTK, keys whose unshifted keysym is a dead key or level 5
latch have no unshifted codepoint and take this path. With event type
reporting enabled, releases therefore emitted the same text as presses
and duplicated characters in applications such as Neovim.

Skip the raw text fallback for release events while retaining it for
presses and repeats. Keep the guard in the shared encoder so release
events for identified keys still retain the UTF-8 data used to derive
alternate keys.

Cover releases with and without report-all mode, and verify that repeat
events continue to emit fallback text.
This commit is contained in:
Sacha Trémoureux
2026-08-15 12:48:37 +02:00
parent 0ba6250388
commit bd647035e9

View File

@@ -224,9 +224,11 @@ fn kitty(
const entry = entry_ orelse {
// No entry found. If we have UTF-8 text this is a pure text event
// (e.g. composed/IME text), so send it as-is so programs can
// still receive it.
if (event.utf8.len > 0) return try writer.writeAll(event.utf8);
return;
// still receive it. Release events never insert text, same as the
// plain-text path above.
if (event.action == .release) return;
if (event.utf8.len == 0) return;
return try writer.writeAll(event.utf8);
};
// If this is just a modifier we require "report all" to send the sequence.
@@ -1492,6 +1494,50 @@ test "kitty: composed text with report all" {
try testing.expectEqualStrings("\xc3\xbb", writer.buffered());
}
// A key whose base (unshifted) keysym is a dead key has no unshifted
// codepoint, so it has no kitty entry and falls back to writing the text
// directly. That must not happen on release or the text is inserted twice.
test "kitty: text fallback on release" {
for ([_]bool{ false, true }) |report_all| {
var buf: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try kitty(&writer, .{
.action = .release,
.key = .unidentified,
.mods = .{ .shift = true },
.utf8 = "!",
}, .{
.kitty_flags = .{
.disambiguate = true,
.report_events = true,
.report_alternates = true,
.report_all = report_all,
.report_associated = true,
},
});
try testing.expectEqualStrings("", writer.buffered());
}
}
test "kitty: text fallback on repeat" {
var buf: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try kitty(&writer, .{
.action = .repeat,
.key = .unidentified,
.mods = .{ .shift = true },
.utf8 = "!",
}, .{
.kitty_flags = .{
.disambiguate = true,
.report_events = true,
.report_alternates = true,
.report_associated = true,
},
});
try testing.expectEqualStrings("!", writer.buffered());
}
test "kitty: shift+a on US keyboard" {
var buf: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);