From 07e6cf900f78c0303116832e3dc93a2bda992aec Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Fri, 24 Nov 2023 13:56:47 -0600 Subject: [PATCH 1/3] key-encode: only set associated text when there is printable text Associated text should only be sent to the terminal when printable text is generated from the keypress. Prevent sending associated text when any modifier is pressed, except for Shift, NumLock, and Capslock This brings Ghostty inline with the output of Kitty. --- src/input/KeyEncoder.zig | 57 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/input/KeyEncoder.zig b/src/input/KeyEncoder.zig index aa58f3953..78b8a098f 100644 --- a/src/input/KeyEncoder.zig +++ b/src/input/KeyEncoder.zig @@ -175,7 +175,7 @@ fn kitty( } } - if (self.kitty_flags.report_associated) { + if (self.kitty_flags.report_associated and !seq.mods.preventsText()) { seq.text = self.event.utf8; } @@ -585,6 +585,19 @@ const KittyMods = packed struct(u8) { }; } + /// Returns true if the modifiers prevent printable text + pub fn preventsText(self: KittyMods) bool { + if (self.alt or + self.ctrl or + self.super or + self.hyper or + self.meta) + { + return true; + } + return false; + } + /// Returns the raw int value of this packed struct. pub fn int(self: KittyMods) u8 { return @bitCast(self); @@ -1167,6 +1180,48 @@ test "kitty: left shift with report all" { try testing.expectEqualStrings("\x1b[57441u", actual); } +test "kitty: report associated with modifiers" { + var buf: [128]u8 = undefined; + var enc: KeyEncoder = .{ + .event = .{ + .key = .j, + .mods = .{ .ctrl = true }, + .utf8 = "j", + .unshifted_codepoint = 106, + }, + .kitty_flags = .{ + .disambiguate = true, + .report_all = true, + .report_alternates = true, + .report_associated = true, + }, + }; + + const actual = try enc.kitty(&buf); + try testing.expectEqualStrings("\x1b[106;5u", actual); +} + +test "kitty: report associated" { + var buf: [128]u8 = undefined; + var enc: KeyEncoder = .{ + .event = .{ + .key = .j, + .mods = .{ .shift = true }, + .utf8 = "J", + .unshifted_codepoint = 106, + }, + .kitty_flags = .{ + .disambiguate = true, + .report_all = true, + .report_alternates = true, + .report_associated = true, + }, + }; + + const actual = try enc.kitty(&buf); + try testing.expectEqualStrings("\x1b[106:74;2;74u", actual); +} + test "kitty: alternates omit control characters" { var buf: [128]u8 = undefined; var enc: KeyEncoder = .{ From 659b43de378e830a0e8e4b7cfcb49809f501ba60 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 29 Nov 2023 21:34:30 -0800 Subject: [PATCH 2/3] input: Kitty encoding suppress associated text on macOS with alt --- src/input/KeyEncoder.zig | 72 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/src/input/KeyEncoder.zig b/src/input/KeyEncoder.zig index 78b8a098f..d4b6a5cad 100644 --- a/src/input/KeyEncoder.zig +++ b/src/input/KeyEncoder.zig @@ -175,7 +175,29 @@ fn kitty( } } - if (self.kitty_flags.report_associated and !seq.mods.preventsText()) { + if (self.kitty_flags.report_associated) associated: { + if (comptime builtin.target.isDarwin()) { + // macOS has special logic because alt+key can produce unicode + // characters. If we are treating option as alt, then we do NOT + // report associated text. If we are not treating alt as alt, + // we do. + if (switch (self.macos_option_as_alt) { + .left => all_mods.sides.alt == .left, + .right => all_mods.sides.alt == .right, + .true => true, + + // This is the main weird one. If we are NOT treating + // option as alt, we still want to prevent text if we + // have modifiers set WITHOUT alt. If alt is present, + // macOS will handle generating the unicode character. + // If alt is not present, we want to suppress. + .false => !seq.mods.alt and seq.mods.preventsText(), + }) break :associated; + } else { + // If any modifiers are present, we don't report associated text. + if (seq.mods.preventsText()) break :associated; + } + seq.text = self.event.utf8; } @@ -1180,6 +1202,54 @@ test "kitty: left shift with report all" { try testing.expectEqualStrings("\x1b[57441u", actual); } +test "kitty: report associated with alt text on macOS with option" { + if (comptime !builtin.target.isDarwin()) return error.SkipZigTest; + + var buf: [128]u8 = undefined; + var enc: KeyEncoder = .{ + .event = .{ + .key = .w, + .mods = .{ .alt = true }, + .utf8 = "∑", + .unshifted_codepoint = 119, + }, + .kitty_flags = .{ + .disambiguate = true, + .report_all = true, + .report_alternates = true, + .report_associated = true, + }, + .macos_option_as_alt = .false, + }; + + const actual = try enc.kitty(&buf); + try testing.expectEqualStrings("\x1b[119;3;8721u", actual); +} + +test "kitty: report associated with alt text on macOS with alt" { + if (comptime !builtin.target.isDarwin()) return error.SkipZigTest; + + var buf: [128]u8 = undefined; + var enc: KeyEncoder = .{ + .event = .{ + .key = .w, + .mods = .{ .alt = true }, + .utf8 = "∑", + .unshifted_codepoint = 119, + }, + .kitty_flags = .{ + .disambiguate = true, + .report_all = true, + .report_alternates = true, + .report_associated = true, + }, + .macos_option_as_alt = .true, + }; + + const actual = try enc.kitty(&buf); + try testing.expectEqualStrings("\x1b[119;3u", actual); +} + test "kitty: report associated with modifiers" { var buf: [128]u8 = undefined; var enc: KeyEncoder = .{ From bc7643eae973b7e9da1a92ad4f08257df84ec6ec Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 29 Nov 2023 21:35:47 -0800 Subject: [PATCH 3/3] input: update preventsText comment --- src/input/KeyEncoder.zig | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/input/KeyEncoder.zig b/src/input/KeyEncoder.zig index d4b6a5cad..887e522fc 100644 --- a/src/input/KeyEncoder.zig +++ b/src/input/KeyEncoder.zig @@ -607,17 +607,16 @@ const KittyMods = packed struct(u8) { }; } - /// Returns true if the modifiers prevent printable text + /// Returns true if the modifiers prevent printable text. + /// + /// Note on macOS: this logic alone is not enough, since you must + /// consider macos_option_as_alt. See the Kitty encoder for more details. pub fn preventsText(self: KittyMods) bool { - if (self.alt or + return self.alt or self.ctrl or self.super or self.hyper or - self.meta) - { - return true; - } - return false; + self.meta; } /// Returns the raw int value of this packed struct.