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 },