From cba27e26cf246984f96aff2a30f4272f18e75842 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 9 Jan 2024 11:57:09 -0800 Subject: [PATCH] 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. --- src/Surface.zig | 1 + src/input/KeyEncoder.zig | 87 +++++++++++++++++++++++++++++++++++++++- src/terminal/modes.zig | 1 + 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/Surface.zig b/src/Surface.zig index a25b2fe74..0a7be6aa2 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -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(), }; diff --git a/src/input/KeyEncoder.zig b/src/input/KeyEncoder.zig index c3f05d6d4..473ad12d0 100644 --- a/src/input/KeyEncoder.zig +++ b/src/input/KeyEncoder.zig @@ -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 = .{ diff --git a/src/terminal/modes.zig b/src/terminal/modes.zig index 0f010d212..dab87c718 100644 --- a/src/terminal/modes.zig +++ b/src/terminal/modes.zig @@ -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 },