Don't encode option as alt in modify other keys 2 (#9406)

There have been frequent reports of key encoding issues in vim and tmux
with version 1.2.3 on macOS: #9340, #9361, #9401,
https://discord.com/channels/1005603569187160125/1432413679806320772.

I think I found the culprit: the option modifier is always passed as alt
to the core, regardless of `macos-option-as-alt`. Since #9289, this
means that a key event where option was used (as option) for translation
is encoded as if it also has the alt modifier.

For example, consider the many European keyboard layouts where option+8
sends `[`. If `macos-option-as-alt = true`, Ghostty correctly intercepts
the option and encodes option+8 as alt+8 instead (that is,
`^[[27;3;56~`). But if `macos-option-as-alt = false`, Ghostty first
allows option to be used for translation, obtaining `[`, and then
encodes the key event as alt+[ (that is, `^[[27;3;91~`), rather than
just `[`.

Tweaking the test case from #9289, here's a quick way to see this: set
`macos-option-as-alt = left`, run
```
printf '\033[>4;2m'
cat
```
choose a European keyboard layout (e.g., Norwegian), and hit both
left-option+8 and right-option+8. The former inserts `^[[27;3;56~` in
all well-behaved terminals. The latter inserts `[` in other terminals,
but `^[[27;3;91~` in Ghostty.

Basically, while modify other keys 2 does require encoding consumed
modifiers, the option key is not one of the supported modifiers, and
should not be included (as alt or anything else) when
`macos-option-as-alt = false`.

This PR removes alts that were actually options when using modify other
keys 2.
pull/9407/head
Daniel Wennberg 2025-10-29 20:29:53 -07:00 committed by GitHub
parent a4d54dca1c
commit e70ca0b9b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 45 additions and 2 deletions

View File

@ -412,8 +412,20 @@ fn legacy(
if (it.nextCodepoint() != null) break :modify_other;
// The mods we encode for this are just the binding mods (shift, ctrl,
// super, alt).
const mods = event.mods.binding();
// super, alt unless it is actually option).
const mods = mods: {
var mods_binding = event.mods.binding();
if (comptime builtin.target.os.tag.isDarwin()) alt: {
switch (opts.macos_option_as_alt) {
.false => {},
.true => break :alt,
.left => if (event.mods.sides.alt == .left) break :alt,
.right => if (event.mods.sides.alt == .right) break :alt,
}
mods_binding.alt = false;
}
break :mods mods_binding;
};
// This copies xterm's `ModifyOtherKeys` function that returns
// whether modify other keys should be encoded for the given
@ -1988,6 +2000,37 @@ test "legacy: ctrl+shift+char with modify other state 2 and consumed mods" {
try testing.expectEqualStrings("\x1b[27;6;72~", writer.buffered());
}
test "legacy: alt+digit with modify other state 2" {
var buf: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try legacy(&writer, .{
.key = .digit_8,
.mods = .{ .alt = true },
.consumed_mods = .{},
.utf8 = "8",
}, .{
.modify_other_keys_state_2 = true,
.macos_option_as_alt = .true,
});
try testing.expectEqualStrings("\x1b[27;3;56~", writer.buffered());
}
test "legacy: alt+digit with modify other state 2 and macos-option-as-alt = false" {
if (comptime builtin.os.tag != .macos) return error.SkipZigTest;
var buf: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try legacy(&writer, .{
.key = .digit_8,
.mods = .{ .alt = true },
.consumed_mods = .{ .alt = true },
.utf8 = "[", // common translation of option+8 with European keyboard layouts
}, .{
.modify_other_keys_state_2 = true,
.macos_option_as_alt = .false,
});
try testing.expectEqualStrings("[", writer.buffered());
}
test "legacy: fixterm awkward letters" {
var buf: [128]u8 = undefined;
{