mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-25 00:21:46 +00:00
apprt/embedded: initialize the TIS keymap lazily
The embedded apprt App init created the keyboard layout keymap eagerly, which requires talking to the text input system (TIS). The first TIS call in a process is slow: 6.6ms measured inside ghostty_app_new during app launch (up to ~30ms in a cold process). The keymap is only used for keyboard layout queries (option-as-alt detection, layout change reload), which happen once keyboard events are flowing. By then AppKit has already warmed TIS and the call is effectively free (~0.2us measured warm). So initialize the keymap lazily on first use. If the layout changes before the keymap was ever created, reload is a no-op since lazy init picks up the current layout. Measured on macOS (Apple Silicon) with local timing instrumentation during app launch: embedded app init before: ~6.7ms (keymap 6614us) embedded app init after: ~60us (config clone only)
This commit is contained in:
@@ -120,7 +120,13 @@ pub const App = struct {
|
||||
|
||||
core_app: *CoreApp,
|
||||
opts: Options,
|
||||
keymap: input.Keymap,
|
||||
|
||||
/// The keyboard layout keymap. This is lazily initialized on first
|
||||
/// use because creating it requires talking to the text input
|
||||
/// system (TIS on macOS), and the first such call in a process is
|
||||
/// slow (multiple milliseconds). It is only needed once keyboard
|
||||
/// events start flowing, at which point the system is warm.
|
||||
keymap: ?input.Keymap,
|
||||
|
||||
/// The configuration for the app. This is owned by this structure.
|
||||
config: Config,
|
||||
@@ -136,19 +142,16 @@ pub const App = struct {
|
||||
var config_clone = try config.clone(alloc);
|
||||
errdefer config_clone.deinit();
|
||||
|
||||
var keymap = try input.Keymap.init();
|
||||
errdefer keymap.deinit();
|
||||
|
||||
self.* = .{
|
||||
.core_app = core_app,
|
||||
.config = config_clone,
|
||||
.opts = opts,
|
||||
.keymap = keymap,
|
||||
.keymap = null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn terminate(self: *App) void {
|
||||
self.keymap.deinit();
|
||||
if (self.keymap) |*v| v.deinit();
|
||||
self.config.deinit();
|
||||
}
|
||||
|
||||
@@ -208,8 +211,10 @@ pub const App = struct {
|
||||
|
||||
/// This should be called whenever the keyboard layout was changed.
|
||||
pub fn reloadKeymap(self: *App) !void {
|
||||
// Reload the keymap
|
||||
try self.keymap.reload();
|
||||
// Reload the keymap. If it was never initialized we don't need
|
||||
// to do anything since lazy initialization will pick up the
|
||||
// current layout.
|
||||
if (self.keymap) |*v| try v.reload();
|
||||
}
|
||||
|
||||
/// Loads the keyboard layout.
|
||||
@@ -217,13 +222,25 @@ pub const App = struct {
|
||||
/// Kind of expensive so this should be avoided if possible. When I say
|
||||
/// "kind of expensive" I mean that its not something you probably want
|
||||
/// to run on every keypress.
|
||||
pub fn keyboardLayout(self: *const App) input.KeyboardLayout {
|
||||
pub fn keyboardLayout(self: *App) input.KeyboardLayout {
|
||||
// We only support keyboard layout detection on macOS.
|
||||
if (comptime builtin.os.tag != .macos) return .unknown;
|
||||
|
||||
// Lazily initialize the keymap.
|
||||
const keymap: *input.Keymap = keymap: {
|
||||
if (self.keymap == null) {
|
||||
self.keymap = input.Keymap.init() catch |err| {
|
||||
log.warn("error initializing keymap err={}", .{err});
|
||||
return .unknown;
|
||||
};
|
||||
}
|
||||
|
||||
break :keymap &self.keymap.?;
|
||||
};
|
||||
|
||||
// Any layout larger than this is not something we can handle.
|
||||
var buf: [256]u8 = undefined;
|
||||
const id = self.keymap.sourceId(&buf) catch |err| {
|
||||
const id = keymap.sourceId(&buf) catch |err| {
|
||||
comptime assert(@TypeOf(err) == error{OutOfMemory});
|
||||
return .unknown;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user