mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-20 02:36:22 +03:00
macos: use the new self-hosted translation
This commit is contained in:
@ -349,7 +349,15 @@ extension Ghostty {
|
|||||||
let action = event.isARepeat ? GHOSTTY_ACTION_REPEAT : GHOSTTY_ACTION_PRESS
|
let action = event.isARepeat ? GHOSTTY_ACTION_REPEAT : GHOSTTY_ACTION_PRESS
|
||||||
keyAction(action, event: event)
|
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) {
|
override func keyUp(with event: NSEvent) {
|
||||||
@ -359,28 +367,7 @@ extension Ghostty {
|
|||||||
private func keyAction(_ action: ghostty_input_action_e, event: NSEvent) {
|
private func keyAction(_ action: ghostty_input_action_e, event: NSEvent) {
|
||||||
guard let surface = self.surface else { return }
|
guard let surface = self.surface else { return }
|
||||||
let mods = Self.translateFlags(event.modifierFlags)
|
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)
|
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
|
// MARK: Menu Handlers
|
||||||
|
@ -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 {
|
pub fn charCallback(self: *Surface, cp_: u32) void {
|
||||||
const cp = std.math.cast(u21, cp_) orelse return;
|
const cp = std.math.cast(u21, cp_) orelse return;
|
||||||
self.core_surface.charCallback(cp) catch |err| {
|
self.core_surface.charCallback(cp) catch |err| {
|
||||||
@ -573,27 +648,14 @@ pub const CAPI = struct {
|
|||||||
keycode: u32,
|
keycode: u32,
|
||||||
c_mods: c_int,
|
c_mods: c_int,
|
||||||
) void {
|
) void {
|
||||||
if (action != .press) return;
|
surface.key2Callback(
|
||||||
|
|
||||||
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}", .{
|
|
||||||
action,
|
action,
|
||||||
keycode,
|
keycode,
|
||||||
result.composing,
|
@bitCast(@as(u8, @truncate(@as(c_uint, @bitCast(c_mods))))),
|
||||||
result.text,
|
) catch |err| {
|
||||||
result.text,
|
log.err("error processing key event err={}", .{err});
|
||||||
});
|
return;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell the surface that it needs to schedule a render
|
/// Tell the surface that it needs to schedule a render
|
||||||
|
@ -2,6 +2,7 @@ const std = @import("std");
|
|||||||
|
|
||||||
pub usingnamespace @import("input/mouse.zig");
|
pub usingnamespace @import("input/mouse.zig");
|
||||||
pub usingnamespace @import("input/key.zig");
|
pub usingnamespace @import("input/key.zig");
|
||||||
|
pub const keycodes = @import("input/keycodes.zig");
|
||||||
pub const Binding = @import("input/Binding.zig");
|
pub const Binding = @import("input/Binding.zig");
|
||||||
pub const Keymap = @import("input/Keymap.zig");
|
pub const Keymap = @import("input/Keymap.zig");
|
||||||
pub const SplitDirection = Binding.Action.SplitDirection;
|
pub const SplitDirection = Binding.Action.SplitDirection;
|
||||||
|
@ -180,4 +180,67 @@ pub const Key = enum(c_int) {
|
|||||||
|
|
||||||
// To support more keys (there are obviously more!) add them here
|
// To support more keys (there are obviously more!) add them here
|
||||||
// and ensure the mapping is up to date in the Window key handler.
|
// 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user