input: manage application keypad state with mode 1035

Fixes #1099

We previously applied application keypad mode logic (`ESC=` or mode 66)
whenever it was active. However, from looking at the behavior of other
terminals (xterm and foot) it appears this isn't correct.

For xterm, application keypad mode only applies unconditionally if the
keyboard mode is VT220 (`-kt vt220`). For modern terminals, application
keypad mode is only applied if mode 1035 is disabled.

Mode 1035 is the "ignore numpad state with keypad mode" mode. It
defaults to true on terminal startup. If this is true, keypads are
always encoded in numerical mode. If this is false, the numlock state
will be respected.
This commit is contained in:
Mitchell Hashimoto
2024-01-09 11:57:09 -08:00
parent dcc3c17738
commit cba27e26cf
3 changed files with 88 additions and 1 deletions

View File

@ -1322,6 +1322,7 @@ pub fn keyCallback(
.alt_esc_prefix = t.modes.get(.alt_esc_prefix),
.cursor_key_application = t.modes.get(.cursor_keys),
.keypad_key_application = t.modes.get(.keypad_keys),
.ignore_keypad_with_numlock = t.modes.get(.ignore_keypad_with_numlock),
.modify_other_keys_state_2 = t.flags.modify_other_keys_2,
.kitty_flags = t.screen.kitty_keyboard.current(),
};

View File

@ -26,6 +26,7 @@ macos_option_as_alt: config.OptionAsAlt = .false,
alt_esc_prefix: bool = false,
cursor_key_application: bool = false,
keypad_key_application: bool = false,
ignore_keypad_with_numlock: bool = false,
modify_other_keys_state_2: bool = false,
kitty_flags: KittyFlags = .{},
@ -245,6 +246,7 @@ fn legacy(
all_mods,
self.cursor_key_application,
self.keypad_key_application,
self.ignore_keypad_with_numlock,
self.modify_other_keys_state_2,
)) |sequence| pc_style: {
// If we're pressing enter and have UTF-8 text, we probably are
@ -416,7 +418,8 @@ fn pcStyleFunctionKey(
keyval: key.Key,
mods: key.Mods,
cursor_key_application: bool,
keypad_key_application: bool,
keypad_key_application_req: bool,
ignore_keypad_with_numlock: bool,
modify_other_keys: bool, // True if state 2
) ?[]const u8 {
// We only want binding-sensitive mods because lock keys
@ -424,6 +427,24 @@ fn pcStyleFunctionKey(
// pc-style function keys.
const mods_int = mods.binding().int();
// Keypad application keymode isn't super straightforward.
// On xterm, in VT220 mode, numlock alone is enough to trigger
// application mode. But in more modern modes, numlock is
// ignored by default via mode 1035 (default true). If mode
// 1035 is on, we always are in numerical keypad mode. If
// mode 1035 is off, we are in application mode if the
// proper numlock state is pressed. The numlock state is implicitly
// determined based on the keycode sent (i.e. 1 with numlock
// on will be kp_end).
const keypad_key_application = keypad: {
// If we're ignoring keypad then this is always false.
// In other words, we're always in numerical keypad mode.
if (ignore_keypad_with_numlock) break :keypad false;
// If we're not ignoring then we enable the desired state.
break :keypad keypad_key_application_req;
};
for (function_keys.keys.get(keyval)) |entry| {
switch (entry.cursor) {
.any => {},
@ -1677,6 +1698,70 @@ test "legacy: keypad enter" {
try testing.expectEqualStrings("\r", actual);
}
test "legacy: keypad 1" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .kp_1,
.mods = .{},
.consumed_mods = .{},
.utf8 = "1",
},
};
const actual = try enc.legacy(&buf);
try testing.expectEqualStrings("1", actual);
}
test "legacy: keypad 1 with application keypad" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .kp_1,
.mods = .{},
.consumed_mods = .{},
.utf8 = "1",
},
.keypad_key_application = true,
};
const actual = try enc.legacy(&buf);
try testing.expectEqualStrings("\x1bOq", actual);
}
test "legacy: keypad 1 with application keypad and numlock" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .kp_1,
.mods = .{ .num_lock = true },
.consumed_mods = .{},
.utf8 = "1",
},
.keypad_key_application = true,
};
const actual = try enc.legacy(&buf);
try testing.expectEqualStrings("\x1bOq", actual);
}
test "legacy: keypad 1 with application keypad and numlock ignore" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{
.event = .{
.key = .kp_1,
.mods = .{ .num_lock = false },
.consumed_mods = .{},
.utf8 = "1",
},
.keypad_key_application = true,
.ignore_keypad_with_numlock = true,
};
const actual = try enc.legacy(&buf);
try testing.expectEqualStrings("1", actual);
}
test "legacy: f1" {
var buf: [128]u8 = undefined;
var enc: KeyEncoder = .{

View File

@ -206,6 +206,7 @@ const entries: []const ModeEntry = &.{
.{ .name = "mouse_alternate_scroll", .value = 1007, .default = true },
.{ .name = "mouse_format_urxvt", .value = 1015 },
.{ .name = "mouse_format_sgr_pixels", .value = 1016 },
.{ .name = "ignore_keypad_with_numlock", .value = 1035, .default = true },
.{ .name = "alt_esc_prefix", .value = 1036, .default = true },
.{ .name = "alt_sends_escape", .value = 1039 },
.{ .name = "reverse_wrap_extended", .value = 1045 },