macos: use the new self-hosted translation

This commit is contained in:
Mitchell Hashimoto
2023-08-10 15:04:15 -07:00
parent 6d439c06af
commit 1e1ad7deb9
4 changed files with 154 additions and 41 deletions

View File

@ -349,7 +349,15 @@ extension Ghostty {
let action = event.isARepeat ? GHOSTTY_ACTION_REPEAT : GHOSTTY_ACTION_PRESS
keyAction(action, event: event)
self.interpretKeyEvents([event])
// We specifically DO NOT call interpretKeyEvents because ghostty_surface_key
// automatically handles all key translation, and we don't handle any commands
// currently.
//
// It is possible that in the future we'll have to modify ghostty_surface_key
// and the embedding API so that we can call this because macOS needs to do
// some things with certain keys. I'm not sure. For now this works.
//
// self.interpretKeyEvents([event])
}
override func keyUp(with event: NSEvent) {
@ -359,28 +367,7 @@ extension Ghostty {
private func keyAction(_ action: ghostty_input_action_e, event: NSEvent) {
guard let surface = self.surface else { return }
let mods = Self.translateFlags(event.modifierFlags)
let unmapped_key = Self.keycodes[event.keyCode] ?? GHOSTTY_KEY_INVALID
ghostty_surface_key2(surface, action, UInt32(event.keyCode), mods)
// We translate the key to the localized keyboard layout. However, we only support
// ASCII characters to make our translation easier across platforms. This is something
// we want to make a lot more robust in the future, so this will hopefully change.
// For now, this makes most keyboard layouts work, and for those that don't, they can
// use physical keycode mappings.
let key = {
if let str = event.characters(byApplyingModifiers: .init(rawValue: 0)) {
if str.utf8.count == 1, let firstByte = str.utf8.first {
if let translatedKey = Self.ascii[firstByte] {
return translatedKey
}
}
}
return unmapped_key
}()
ghostty_surface_key(surface, action, key, unmapped_key, mods)
}
// MARK: Menu Handlers

View File

@ -395,6 +395,81 @@ pub const Surface = struct {
};
}
pub fn key2Callback(
self: *Surface,
action: input.Action,
keycode: u32,
mods: input.Mods,
) !void {
if (action != .press) return;
// Translate our key using the keymap for our localized keyboard layout.
var buf: [128]u8 = undefined;
const result = try self.app.keymap.translate(
&buf,
&self.keymap_state,
@intCast(keycode),
mods,
);
log.warn("TRANSLATE: action={} keycode={x} dead={} key={any} key_str={s} mods={}", .{
action,
keycode,
result.composing,
result.text,
result.text,
mods,
});
// If this is a dead key, then we're composing a character and
// we end processing here. We don't process keybinds for dead keys.
if (result.composing) {
// TODO: we ultimately want to update some surface state so that
// we can show the user that we're in dead key mode and the
// precomposed character. For now, we can just ignore and that
// is not incorrect behavior.
log.warn("dead key mode, currently composing", .{});
return;
}
// We want to get the physical unmapped key to process keybinds.
const physical_key = keycode: for (input.keycodes.entries) |entry| {
if (entry.native == keycode) break :keycode entry.key;
} else .invalid;
// If the resulting text has length 1 then we can take its key
// and attempt to translate it to a key enum and call the key callback.
// If the length is greater than 1 then we're going to call the
// charCallback.
const key = if (result.text.len == 1) key: {
// A completed key. If the length of the key is one then we can
// attempt to translate it to a key enum and call the key callback.
break :key input.Key.fromASCII(result.text[0]) orelse physical_key;
} else .invalid;
// If both keys are invalid then we won't call the key callback. But
// if either one is valid, we want to give it a chance.
if (key != .invalid or physical_key != .invalid) {
self.core_surface.keyCallback(action, key, physical_key, mods) catch |err| {
log.err("error in key callback err={}", .{err});
return;
};
}
// Next, we want to call the char callback with each codepoint.
const view = std.unicode.Utf8View.init(result.text) catch |err| {
log.warn("cannot build utf8 view over input: {}", .{err});
return;
};
var it = view.iterator();
while (it.nextCodepoint()) |cp| {
self.core_surface.charCallback(cp) catch |err| {
log.err("error in char callback err={}", .{err});
return;
};
}
}
pub fn charCallback(self: *Surface, cp_: u32) void {
const cp = std.math.cast(u21, cp_) orelse return;
self.core_surface.charCallback(cp) catch |err| {
@ -573,27 +648,14 @@ pub const CAPI = struct {
keycode: u32,
c_mods: c_int,
) void {
if (action != .press) return;
var buf: [128]u8 = undefined;
const mods: input.Mods = @bitCast(@as(u8, @truncate(@as(c_uint, @bitCast(c_mods)))));
const result = surface.app.keymap.translate(
&buf,
&surface.keymap_state,
@intCast(keycode),
mods,
) catch |err| {
log.err("TRANSLATE error translating key err={}", .{err});
return;
};
log.warn("TRANSLATE: action={} keycode={x} dead={} key={any} key_str={s}", .{
surface.key2Callback(
action,
keycode,
result.composing,
result.text,
result.text,
});
@bitCast(@as(u8, @truncate(@as(c_uint, @bitCast(c_mods))))),
) catch |err| {
log.err("error processing key event err={}", .{err});
return;
};
}
/// Tell the surface that it needs to schedule a render

View File

@ -2,6 +2,7 @@ const std = @import("std");
pub usingnamespace @import("input/mouse.zig");
pub usingnamespace @import("input/key.zig");
pub const keycodes = @import("input/keycodes.zig");
pub const Binding = @import("input/Binding.zig");
pub const Keymap = @import("input/Keymap.zig");
pub const SplitDirection = Binding.Action.SplitDirection;

View File

@ -180,4 +180,67 @@ pub const Key = enum(c_int) {
// To support more keys (there are obviously more!) add them here
// and ensure the mapping is up to date in the Window key handler.
/// Converts an ASCII character to a key, if possible. This returns
/// null if the character is unknown.
///
/// Note that this can't distinguish between physical keys, i.e. '0'
/// may be from the number row or the keypad, but it always maps
/// to '.zero'.
///
/// This is what we want, we awnt people to create keybindings that
/// are independent of the physical key.
pub fn fromASCII(ch: u8) ?Key {
return switch (ch) {
'a' => .a,
'b' => .b,
'c' => .c,
'd' => .d,
'e' => .e,
'f' => .f,
'g' => .g,
'h' => .h,
'i' => .i,
'j' => .j,
'k' => .k,
'l' => .l,
'm' => .m,
'n' => .n,
'o' => .o,
'p' => .p,
'q' => .q,
'r' => .r,
's' => .s,
't' => .t,
'u' => .u,
'v' => .v,
'w' => .w,
'x' => .x,
'y' => .y,
'z' => .z,
'0' => .zero,
'1' => .one,
'2' => .two,
'3' => .three,
'4' => .four,
'5' => .five,
'6' => .six,
'7' => .seven,
'8' => .eight,
'9' => .nine,
';' => .semicolon,
' ' => .space,
'\'' => .apostrophe,
',' => .comma,
'`' => .grave_accent,
'.' => .period,
'/' => .slash,
'-' => .minus,
'=' => .equal,
'[' => .left_bracket,
']' => .right_bracket,
'\\' => .backslash,
else => null,
};
}
};