summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitchell Hashimoto <m@mitchellh.com>2025-02-13 14:45:31 -0800
committerMitchell Hashimoto <m@mitchellh.com>2025-02-13 14:58:41 -0800
commitb44b1086d355fc269dde5979bc69e85cdf1990b2 (patch)
tree64eafc1b9508af3c8c688ec76dcb5b65d53005e8
parent9978ea3b9c0312fe4a27bf818d8fc988441bcaba (diff)
apprt/embedded: proper consumed modifier state for ctrl keys
-rw-r--r--macos/Sources/Ghostty/SurfaceView_AppKit.swift24
-rw-r--r--src/apprt/embedded.zig20
-rw-r--r--src/input/KeymapDarwin.zig25
-rw-r--r--src/input/KeymapNoop.zig5
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 {