apprt/embedded: proper consumed modifier state for ctrl keys

This commit is contained in:
Mitchell Hashimoto
2025-02-13 14:45:31 -08:00
parent 9978ea3b9c
commit b44b1086d3
4 changed files with 32 additions and 42 deletions

View File

@@ -849,28 +849,8 @@ extension Ghostty {
var handled: Bool = false var handled: Bool = false
if let list = keyTextAccumulator, list.count > 0 { if let list = keyTextAccumulator, list.count > 0 {
handled = true 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 { for text in list {
_ = keyAction(action, event: keyTextEvent, text: text) _ = keyAction(action, event: event, text: text)
}
} }
} }

View File

@@ -182,14 +182,9 @@ pub const App = struct {
if (strip) translate_mods.alt = false; if (strip) translate_mods.alt = false;
} }
// On macOS we strip ctrl because UCKeyTranslate // We strip super on macOS because its not used for translation
// converts to the masked values (i.e. ctrl+c becomes 3) // it results in a bad translation.
// 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()) { if (comptime builtin.target.isDarwin()) {
translate_mods.ctrl = false;
translate_mods.super = false; translate_mods.super = false;
} }
@@ -229,6 +224,7 @@ pub const App = struct {
const result: input.Keymap.Translation = if (event_text) |text| .{ const result: input.Keymap.Translation = if (event_text) |text| .{
.text = text, .text = text,
.composing = event.composing, .composing = event.composing,
.mods = translate_mods,
} else try self.keymap.translate( } else try self.keymap.translate(
&buf, &buf,
switch (target) { switch (target) {
@@ -273,16 +269,12 @@ pub const App = struct {
// then we clear the text. We handle non-printables in the // then we clear the text. We handle non-printables in the
// key encoder manual (such as tab, ctrl+c, etc.) // key encoder manual (such as tab, ctrl+c, etc.)
if (result.text.len == 1 and result.text[0] < 0x20) { if (result.text.len == 1 and result.text[0] < 0x20) {
break :translate .{ .composing = false, .text = "" }; break :translate .{};
} }
} }
break :translate result; break :translate result;
} else .{ .composing = false, .text = "" }; } else .{};
// 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 .{};
// We need to always do a translation with no modifiers at all in // We need to always do a translation with no modifiers at all in
// order to get the "unshifted_codepoint" for the key event. // order to get the "unshifted_codepoint" for the key event.
@@ -354,7 +346,7 @@ pub const App = struct {
.key = key, .key = key,
.physical_key = physical_key, .physical_key = physical_key,
.mods = mods, .mods = mods,
.consumed_mods = consumed_mods, .consumed_mods = result.mods,
.composing = result.composing, .composing = result.composing,
.utf8 = result.text, .utf8 = result.text,
.unshifted_codepoint = unshifted_codepoint, .unshifted_codepoint = unshifted_codepoint,

View File

@@ -50,10 +50,13 @@ pub const State = struct {
pub const Translation = struct { pub const Translation = struct {
/// The translation result. If this is a dead key state, then this will /// 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. /// 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. /// 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 { pub fn init() !Keymap {
@@ -122,8 +125,18 @@ pub fn translate(
out: []u8, out: []u8,
state: *State, state: *State,
code: u16, code: u16,
mods: Mods, input_mods: Mods,
) !Translation { ) !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. // Get the keycode for the space key, using comptime.
const code_space: u16 = comptime space: for (codes) |entry| { const code_space: u16 = comptime space: for (codes) |entry| {
if (std.mem.eql(u8, entry.code, "Space")) if (std.mem.eql(u8, entry.code, "Space"))
@@ -183,7 +196,11 @@ pub fn translate(
// Convert the utf16 to utf8 // Convert the utf16 to utf8
const len = try std.unicode.utf16leToUtf8(out, char[0..char_count]); 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. /// Map to the modifiers format used by the UCKeyTranslate function.

View File

@@ -6,8 +6,9 @@ const Mods = @import("key.zig").Mods;
pub const State = struct {}; pub const State = struct {};
pub const Translation = struct { pub const Translation = struct {
text: []const u8, text: []const u8 = "",
composing: bool, composing: bool = false,
mods: Mods = .{},
}; };
pub fn init() !KeymapNoop { pub fn init() !KeymapNoop {