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.
This commit is contained in:
Mitchell Hashimoto
2025-05-12 11:12:04 -07:00
parent 6723c308be
commit ecda5ec327
3 changed files with 28 additions and 8 deletions

View File

@@ -56,15 +56,22 @@ extension NSEvent {
// If we have no characters associated with this event we do nothing.
guard let characters else { return nil }
if characters.count == 1,
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 characters.count == 1,
let scalar = characters.unicodeScalars.first,
scalar.value < 0x20 {
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
}
}

View File

@@ -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.

View File

@@ -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);
}