embedded: route split and close C APIs through performBindingAction (#14261)

## Summary

macOS menus call `ghostty_surface_split`, `split_focus`, and
`request_close` directly, while keybinds go through
`Surface.performBindingAction`. GTK already uses that path for splits
and close. Route the embedded C APIs the same way so menus and keybinds
share one hook.

Behavior without any extra Surface hook is unchanged:
`performBindingAction` still forwards splits to the app and
`close_surface` still closes.

## Test plan

- [x] `zig build test -Demit-macos-app=false` (compiles; these C exports
have no unit test)

## AI disclosure

This change was prepared with Cursor. I reviewed the C API routing and
how it compares to GTK's existing `performBindingAction` path.


Made with [Cursor](https://cursor.com)
This commit is contained in:
Mitchell Hashimoto
2026-09-18 06:28:56 -07:00
committed by GitHub

View File

@@ -2164,16 +2164,30 @@ pub const CAPI = struct {
/// Request that the surface become closed. This will go through the
/// normal trigger process that a close surface input binding would.
export fn ghostty_surface_request_close(ptr: *Surface) void {
ptr.core_surface.close();
// Prefer the binding path so close goes through the same Surface
// hook as a close-surface keybind, rather than a raw close() that
// bypasses it.
const handled = ptr.core_surface.performBindingAction(.{ .close_surface = {} }) catch |err| {
log.err("error requesting close err={}", .{err});
return;
};
if (!handled) ptr.core_surface.close();
}
/// Request that the surface split in the given direction.
export fn ghostty_surface_split(ptr: *Surface, direction: apprt.action.SplitDirection) void {
_ = ptr.app.performAction(
.{ .surface = &ptr.core_surface },
.new_split,
direction,
) catch |err| {
// Menu shortcuts call this C API directly. Route through
// performBindingAction so menus and keybinds share one path,
// matching GTK.
const action: input.Binding.Action = .{
.new_split = switch (direction) {
.right => .right,
.left => .left,
.down => .down,
.up => .up,
},
};
_ = ptr.core_surface.performBindingAction(action) catch |err| {
log.err("error creating new split err={}", .{err});
return;
};
@@ -2184,12 +2198,18 @@ pub const CAPI = struct {
ptr: *Surface,
direction: apprt.action.GotoSplit,
) void {
_ = ptr.app.performAction(
.{ .surface = &ptr.core_surface },
.goto_split,
direction,
) catch |err| {
log.err("error creating new split err={}", .{err});
const action: input.Binding.Action = .{
.goto_split = switch (direction) {
.previous => .previous,
.next => .next,
.up => .up,
.down => .down,
.left => .left,
.right => .right,
},
};
_ = ptr.core_surface.performBindingAction(action) catch |err| {
log.err("error focusing split err={}", .{err});
return;
};
}