macos: do not trust AppKit's text translation with ctrl only

Normally, when `ctrl+<character>` is pressed, such as `ctrl+z` or
`ctrl+c`, macOS (AppKit) doesn't do any key translation because that
doesn't map to any printable text on its own. Ghostty does the
translation to correctly determine the character is "z" or "c" or
whatever.

For some reason when the keyboard layout is "Dvorak - QWERTY Cmd"
specifically (_not_ plain "Dvorak") on a US layout keyboard, AppKit
decides that "ctrl+z" ("/" on a qwerty keyboard) translates to "/"...
I can't find any explanation for this.

To workaround this, this commit makes it so that if the following
conditions are true, then we IGNORE AppKit's text translation and
manually do it using UCKeyTranslate:

  (1) We're on macOS specifically (not iOS, etc.)
  (2) We have a key event with ONLY control pressed

This fixes `ctrl+z` on this unique Dvorak keyboard layout.
This commit is contained in:
Mitchell Hashimoto
2024-01-27 08:13:53 -08:00
parent 894554f1a0
commit d177b20bab

View File

@ -765,6 +765,24 @@ pub const Surface = struct {
break :translate_mods translate_mods;
};
const event_text: ?[]const u8 = event_text: {
// This logic only applies to macOS.
if (comptime builtin.os.tag != .macos) break :event_text event.text;
// If the modifiers are ONLY "control" then we never process
// the event text because we want to do our own translation so
// we can handle ctrl+c, ctrl+z, etc.
//
// This is specifically because on macOS using the
// "Dvorak - QWERTY ⌘" keyboard layout, ctrl+z is translated as
// "/" (the physical key that is z on a qwerty keyboard). But on
// other layouts, ctrl+<char> is not translated by AppKit. So,
// we just avoid this by never allowing AppKit to translate
// ctrl+<char> and instead do it ourselves.
const ctrl_only = comptime (input.Mods{ .ctrl = true }).int();
break :event_text if (mods.int() == ctrl_only) null else event.text;
};
// Translate our key using the keymap for our localized keyboard layout.
// We only translate for keydown events. Otherwise, we only care about
// the raw keycode.
@ -772,7 +790,7 @@ pub const Surface = struct {
const result: input.Keymap.Translation = if (is_down) translate: {
// If the event provided us with text, then we use this as a result
// and do not do manual translation.
const result: input.Keymap.Translation = if (event.text) |text| .{
const result: input.Keymap.Translation = if (event_text) |text| .{
.text = text,
.composing = event.composing,
} else try self.app.keymap.translate(