From 9978ea3b9c0312fe4a27bf818d8fc988441bcaba Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 13 Feb 2025 14:20:13 -0800 Subject: [PATCH 1/2] Revert "macos: don't remove ctrl modifier for text input" This reverts commit 3104b217581a9e580155216a260b1a79135b701a. --- .../Sources/Ghostty/SurfaceView_AppKit.swift | 24 +++++++++++++++++-- src/apprt/embedded.zig | 18 +++++++------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 0adc11fa4..f5cb93580 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -849,8 +849,28 @@ extension Ghostty { var handled: Bool = false if let list = keyTextAccumulator, list.count > 0 { handled = true - for text in list { - _ = keyAction(action, event: event, text: text) + + // This is a hack. libghostty on macOS treats ctrl input as not having + // text because some keyboard layouts generate bogus characters for + // ctrl+key. libghostty can't tell this is from an IM keyboard giving + // us direct values. So, we just remove control. + var modifierFlags = event.modifierFlags + modifierFlags.remove(.control) + if let keyTextEvent = NSEvent.keyEvent( + with: .keyDown, + location: event.locationInWindow, + modifierFlags: modifierFlags, + timestamp: event.timestamp, + windowNumber: event.windowNumber, + context: nil, + characters: event.characters ?? "", + charactersIgnoringModifiers: event.charactersIgnoringModifiers ?? "", + isARepeat: event.isARepeat, + keyCode: event.keyCode + ) { + for text in list { + _ = keyAction(action, event: keyTextEvent, text: text) + } } } diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index ffcf7fdbe..c4e69f917 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -182,9 +182,14 @@ pub const App = struct { if (strip) translate_mods.alt = false; } - // We strip super on macOS because its not used for translation - // it results in a bad translation. + // On macOS we strip ctrl because UCKeyTranslate + // converts to the masked values (i.e. ctrl+c becomes 3) + // and we don't want that behavior. + // + // We also strip super because its not used for translation + // on macos and it results in a bad translation. if (comptime builtin.target.isDarwin()) { + translate_mods.ctrl = false; translate_mods.super = false; } @@ -231,14 +236,7 @@ pub const App = struct { .surface => |surface| &surface.keymap_state, }, @intCast(keycode), - if (comptime builtin.target.isDarwin()) mods: { - // On macOS we strip ctrl because UCKeyTranslate - // converts to the masked values (i.e. ctrl+c becomes 3) - // and we don't want that behavior. - var v = translate_mods; - v.ctrl = false; - break :mods v; - } else translate_mods, + translate_mods, ); // TODO(mitchellh): I think we can get rid of the above keymap From b44b1086d355fc269dde5979bc69e85cdf1990b2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 13 Feb 2025 14:45:31 -0800 Subject: [PATCH 2/2] apprt/embedded: proper consumed modifier state for ctrl keys --- .../Sources/Ghostty/SurfaceView_AppKit.swift | 24 ++---------------- src/apprt/embedded.zig | 20 +++++---------- src/input/KeymapDarwin.zig | 25 ++++++++++++++++--- src/input/KeymapNoop.zig | 5 ++-- 4 files changed, 32 insertions(+), 42 deletions(-) diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index f5cb93580..0adc11fa4 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -849,28 +849,8 @@ extension Ghostty { var handled: Bool = false if let list = keyTextAccumulator, list.count > 0 { handled = true - - // This is a hack. libghostty on macOS treats ctrl input as not having - // text because some keyboard layouts generate bogus characters for - // ctrl+key. libghostty can't tell this is from an IM keyboard giving - // us direct values. So, we just remove control. - var modifierFlags = event.modifierFlags - modifierFlags.remove(.control) - if let keyTextEvent = NSEvent.keyEvent( - with: .keyDown, - location: event.locationInWindow, - modifierFlags: modifierFlags, - timestamp: event.timestamp, - windowNumber: event.windowNumber, - context: nil, - characters: event.characters ?? "", - charactersIgnoringModifiers: event.charactersIgnoringModifiers ?? "", - isARepeat: event.isARepeat, - keyCode: event.keyCode - ) { - for text in list { - _ = keyAction(action, event: keyTextEvent, text: text) - } + for text in list { + _ = keyAction(action, event: event, text: text) } } diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index c4e69f917..18674bc38 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -182,14 +182,9 @@ pub const App = struct { if (strip) translate_mods.alt = false; } - // On macOS we strip ctrl because UCKeyTranslate - // converts to the masked values (i.e. ctrl+c becomes 3) - // and we don't want that behavior. - // - // We also strip super because its not used for translation - // on macos and it results in a bad translation. + // We strip super on macOS because its not used for translation + // it results in a bad translation. if (comptime builtin.target.isDarwin()) { - translate_mods.ctrl = false; translate_mods.super = false; } @@ -229,6 +224,7 @@ pub const App = struct { const result: input.Keymap.Translation = if (event_text) |text| .{ .text = text, .composing = event.composing, + .mods = translate_mods, } else try self.keymap.translate( &buf, switch (target) { @@ -273,16 +269,12 @@ pub const App = struct { // then we clear the text. We handle non-printables in the // key encoder manual (such as tab, ctrl+c, etc.) if (result.text.len == 1 and result.text[0] < 0x20) { - break :translate .{ .composing = false, .text = "" }; + break :translate .{}; } } break :translate result; - } else .{ .composing = false, .text = "" }; - - // UCKeyTranslate always consumes all mods, so if we have any output - // then we've consumed our translate mods. - const consumed_mods: input.Mods = if (result.text.len > 0) translate_mods else .{}; + } else .{}; // We need to always do a translation with no modifiers at all in // order to get the "unshifted_codepoint" for the key event. @@ -354,7 +346,7 @@ pub const App = struct { .key = key, .physical_key = physical_key, .mods = mods, - .consumed_mods = consumed_mods, + .consumed_mods = result.mods, .composing = result.composing, .utf8 = result.text, .unshifted_codepoint = unshifted_codepoint, diff --git a/src/input/KeymapDarwin.zig b/src/input/KeymapDarwin.zig index 3d81b0f4b..154f648a6 100644 --- a/src/input/KeymapDarwin.zig +++ b/src/input/KeymapDarwin.zig @@ -50,10 +50,13 @@ pub const State = struct { pub const Translation = struct { /// The translation result. If this is a dead key state, then this will /// be pre-edit text that can be displayed but will ultimately be replaced. - text: []const u8, + text: []const u8 = "", /// Whether the text is still composing, i.e. this is a dead key state. - composing: bool, + composing: bool = false, + + /// The mods that were consumed to produce this translation + mods: Mods = .{}, }; pub fn init() !Keymap { @@ -122,8 +125,18 @@ pub fn translate( out: []u8, state: *State, code: u16, - mods: Mods, + input_mods: Mods, ) !Translation { + // On macOS we strip ctrl because UCKeyTranslate + // converts to the masked values (i.e. ctrl+c becomes 3) + // and we don't want that behavior in Ghostty ever. This makes + // this file not a general-purpose keymap implementation. + const mods: Mods = mods: { + var v = input_mods; + v.ctrl = false; + break :mods v; + }; + // Get the keycode for the space key, using comptime. const code_space: u16 = comptime space: for (codes) |entry| { if (std.mem.eql(u8, entry.code, "Space")) @@ -183,7 +196,11 @@ pub fn translate( // Convert the utf16 to utf8 const len = try std.unicode.utf16leToUtf8(out, char[0..char_count]); - return .{ .text = out[0..len], .composing = composing }; + return .{ + .text = out[0..len], + .composing = composing, + .mods = mods, + }; } /// Map to the modifiers format used by the UCKeyTranslate function. diff --git a/src/input/KeymapNoop.zig b/src/input/KeymapNoop.zig index 414c52954..b6a9d57b9 100644 --- a/src/input/KeymapNoop.zig +++ b/src/input/KeymapNoop.zig @@ -6,8 +6,9 @@ const Mods = @import("key.zig").Mods; pub const State = struct {}; pub const Translation = struct { - text: []const u8, - composing: bool, + text: []const u8 = "", + composing: bool = false, + mods: Mods = .{}, }; pub fn init() !KeymapNoop {