From ecda5ec327288bae5e27c8d2ec1e7ac3a99b3087 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 12 May 2025 11:12:04 -0700 Subject: [PATCH] macos: do not send UTF-8 PUA codepoints to key events Fixes #7337 AppKit encodes functional keys as PUA codepoints. We don't want to send that down as valid text encoding for a key event because KKP uses that in particular to change the encoding with associated text. I think there may be a more specific solution to this by only doing this within the KKP encoding part of KeyEncoder but that was filled with edge cases and I didn't want to risk breaking anything else. --- macos/Sources/Ghostty/NSEvent+Extension.swift | 19 +++++++++++++------ src/input/KeyEncoder.zig | 8 ++++++-- src/terminal/kitty/key.zig | 9 +++++++++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/macos/Sources/Ghostty/NSEvent+Extension.swift b/macos/Sources/Ghostty/NSEvent+Extension.swift index a5aec3870..b67c1932e 100644 --- a/macos/Sources/Ghostty/NSEvent+Extension.swift +++ b/macos/Sources/Ghostty/NSEvent+Extension.swift @@ -56,13 +56,20 @@ extension NSEvent { // If we have no characters associated with this event we do nothing. guard let characters else { return nil } - // If we have a single control character, then we return the characters - // without control pressed. We do this because we handle control character - // encoding directly within Ghostty's KeyEncoder. if characters.count == 1, - let scalar = characters.unicodeScalars.first, - scalar.value < 0x20 { - return self.characters(byApplyingModifiers: modifierFlags.subtracting(.control)) + let scalar = characters.unicodeScalars.first { + // If we have a single control character, then we return the characters + // without control pressed. We do this because we handle control character + // encoding directly within Ghostty's KeyEncoder. + if scalar.value < 0x20 { + return self.characters(byApplyingModifiers: modifierFlags.subtracting(.control)) + } + + // If we have a single value in the PUA, then it's a function key and + // we don't want to send PUA ranges down to Ghostty. + if scalar.value >= 0xF700 && scalar.value <= 0xF8FF { + return nil + } } return characters diff --git a/src/input/KeyEncoder.zig b/src/input/KeyEncoder.zig index 9e68dae2d..41634f2f1 100644 --- a/src/input/KeyEncoder.zig +++ b/src/input/KeyEncoder.zig @@ -146,7 +146,9 @@ fn kitty( // the real world issue is usually control characters. const view = try std.unicode.Utf8View.init(self.event.utf8); var it = view.iterator(); - while (it.nextCodepoint()) |cp| if (isControl(cp)) break :plain_text; + while (it.nextCodepoint()) |cp| { + if (isControl(cp)) break :plain_text; + } return try copyToBuf(buf, self.event.utf8); } @@ -212,7 +214,9 @@ fn kitty( } } - if (self.kitty_flags.report_associated and seq.event != .release) associated: { + if (self.kitty_flags.report_associated and + seq.event != .release) + associated: { // Determine if the Alt modifier should be treated as an actual // modifier (in which case it prevents associated text) or as // the macOS Option key, which does not prevent associated text. diff --git a/src/terminal/kitty/key.zig b/src/terminal/kitty/key.zig index a04bd181a..8bafcb7dc 100644 --- a/src/terminal/kitty/key.zig +++ b/src/terminal/kitty/key.zig @@ -83,6 +83,15 @@ pub const Flags = packed struct(u5) { report_all: bool = false, report_associated: bool = false, + /// Sets all modes on. + pub const @"true": Flags = .{ + .disambiguate = true, + .report_events = true, + .report_alternates = true, + .report_all = true, + .report_associated = true, + }; + pub fn int(self: Flags) u5 { return @bitCast(self); }