core: many more actions

This commit is contained in:
Mitchell Hashimoto
2024-09-26 10:05:04 -07:00
parent 1e010b8e08
commit 9202cba1f5
7 changed files with 280 additions and 133 deletions

View File

@ -841,7 +841,7 @@ fn passwordInput(self: *Surface, v: bool) !void {
self.rt_app.performAction( self.rt_app.performAction(
.{ .surface = self }, .{ .surface = self },
.secure_input, .secure_input,
v, if (v) .on else .off,
) catch |err| { ) catch |err| {
// We ignore this error because we don't want to fail this // We ignore this error because we don't want to fail this
// entire operation just because the apprt failed to set // entire operation just because the apprt failed to set
@ -3685,44 +3685,55 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
}, },
), ),
.new_split => |direction| { .new_split => |direction| try self.rt_app.performAction(
if (@hasDecl(apprt.Surface, "newSplit")) { .{ .surface = self },
try self.rt_surface.newSplit(switch (direction) { .new_split,
.right => .right, switch (direction) {
.down => .down, .right => .right,
.auto => if (self.screen_size.width > self.screen_size.height) .down => .down,
.right .auto => if (self.screen_size.width > self.screen_size.height)
else .right
.down, else
}); .down,
} else log.warn("runtime doesn't implement newSplit", .{}); },
}, ),
.goto_split => |direction| { .goto_split => |direction| try self.rt_app.performAction(
if (@hasDecl(apprt.Surface, "gotoSplit")) { .{ .surface = self },
self.rt_surface.gotoSplit(direction); .goto_split,
} else log.warn("runtime doesn't implement gotoSplit", .{}); switch (direction) {
}, inline else => |tag| @field(
apprt.action.GotoSplit,
@tagName(tag),
),
},
),
.resize_split => |param| { .resize_split => |value| try self.rt_app.performAction(
if (@hasDecl(apprt.Surface, "resizeSplit")) { .{ .surface = self },
const direction = param[0]; .resize_split,
const amount = param[1]; .{
self.rt_surface.resizeSplit(direction, amount); .amount = value[1],
} else log.warn("runtime doesn't implement resizeSplit", .{}); .direction = switch (value[0]) {
}, inline else => |tag| @field(
apprt.action.ResizeSplit.Direction,
@tagName(tag),
),
},
},
),
.equalize_splits => { .equalize_splits => try self.rt_app.performAction(
if (@hasDecl(apprt.Surface, "equalizeSplits")) { .{ .surface = self },
self.rt_surface.equalizeSplits(); .equalize_splits,
} else log.warn("runtime doesn't implement equalizeSplits", .{}); {},
}, ),
.toggle_split_zoom => { .toggle_split_zoom => try self.rt_app.performAction(
if (@hasDecl(apprt.Surface, "toggleSplitZoom")) { .{ .surface = self },
self.rt_surface.toggleSplitZoom(); .toggle_split_zoom,
} else log.warn("runtime doesn't implement toggleSplitZoom", .{}); {},
}, ),
.toggle_fullscreen => { .toggle_fullscreen => {
if (@hasDecl(apprt.Surface, "toggleFullscreen")) { if (@hasDecl(apprt.Surface, "toggleFullscreen")) {
@ -3730,17 +3741,17 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
} else log.warn("runtime doesn't implement toggleFullscreen", .{}); } else log.warn("runtime doesn't implement toggleFullscreen", .{});
}, },
.toggle_window_decorations => { .toggle_window_decorations => try self.rt_app.performAction(
if (@hasDecl(apprt.Surface, "toggleWindowDecorations")) { .{ .surface = self },
self.rt_surface.toggleWindowDecorations(); .toggle_window_decorations,
} else log.warn("runtime doesn't implement toggleWindowDecorations", .{}); {},
}, ),
.toggle_secure_input => { .toggle_secure_input => try self.rt_app.performAction(
if (@hasDecl(apprt.Surface, "toggleSecureInput")) { .{ .surface = self },
self.rt_surface.toggleSecureInput(); .secure_input,
} else log.warn("runtime doesn't implement toggleSecureInput", .{}); .toggle,
}, ),
.select_all => { .select_all => {
const sel = self.io.terminal.screen.selectAll(); const sel = self.io.terminal.screen.selectAll();

View File

@ -32,10 +32,8 @@ pub const ClipboardRequestType = structs.ClipboardRequestType;
pub const ColorScheme = structs.ColorScheme; pub const ColorScheme = structs.ColorScheme;
pub const CursorPos = structs.CursorPos; pub const CursorPos = structs.CursorPos;
pub const DesktopNotification = structs.DesktopNotification; pub const DesktopNotification = structs.DesktopNotification;
pub const GotoTab = structs.GotoTab;
pub const IMEPos = structs.IMEPos; pub const IMEPos = structs.IMEPos;
pub const Selection = structs.Selection; pub const Selection = structs.Selection;
pub const SplitDirection = structs.SplitDirection;
pub const SurfaceSize = structs.SurfaceSize; pub const SurfaceSize = structs.SurfaceSize;
/// The implementation to use for the app runtime. This is comptime chosen /// The implementation to use for the app runtime. This is comptime chosen

View File

@ -21,12 +21,32 @@ pub const Action = union(enum) {
/// the tab should be opened in a new window. /// the tab should be opened in a new window.
new_tab, new_tab,
/// Create a new split. The value determines the location of the split
/// relative to the target.
new_split: SplitDirection,
/// Close all open windows.
close_all_windows,
/// Toggle whether window directions are shown.
toggle_window_decorations,
/// Jump to a specific tab. Must handle the scenario that the tab /// Jump to a specific tab. Must handle the scenario that the tab
/// value is invalid. /// value is invalid.
goto_tab: GotoTab, goto_tab: GotoTab,
/// Close all open windows. /// Jump to a specific split.
close_all_windows, goto_split: GotoSplit,
/// Resize the split in the given direction.
resize_split: ResizeSplit,
/// Equalize all the splits in the target window.
equalize_splits,
/// Toggle whether a split is zoomed or not. A zoomed split is resized
/// to take up the entire window.
toggle_split_zoom,
/// Open the Ghostty configuration. This is platform-specific about /// Open the Ghostty configuration. This is platform-specific about
/// what it means; it can mean opening a dedicated UI or just opening /// what it means; it can mean opening a dedicated UI or just opening
@ -44,7 +64,7 @@ pub const Action = union(enum) {
/// entering a password or other sensitive information. This can be used /// entering a password or other sensitive information. This can be used
/// by the app runtime to change the appearance of the cursor, setup /// by the app runtime to change the appearance of the cursor, setup
/// system APIs to not log the input, etc. /// system APIs to not log the input, etc.
secure_input: bool, secure_input: SecureInput,
/// The enum of keys in the tagged union. /// The enum of keys in the tagged union.
pub const Key = @typeInfo(Action).Union.tag_type.?; pub const Key = @typeInfo(Action).Union.tag_type.?;
@ -68,6 +88,38 @@ pub const Target = union(enum) {
surface: *CoreSurface, surface: *CoreSurface,
}; };
// This is made extern (c_int) to make interop easier with our embedded
// runtime. The small size cost doesn't make a difference in our union.
pub const SplitDirection = enum(c_int) {
right,
down,
};
// This is made extern (c_int) to make interop easier with our embedded
// runtime. The small size cost doesn't make a difference in our union.
pub const GotoSplit = enum(c_int) {
previous,
next,
top,
left,
bottom,
right,
};
/// The amount to resize the split by and the direction to resize it in.
pub const ResizeSplit = struct {
amount: u16,
direction: Direction,
pub const Direction = enum(c_int) {
up,
down,
left,
right,
};
};
/// The tab to jump to. This is non-exhaustive so that integer values represent /// The tab to jump to. This is non-exhaustive so that integer values represent
/// the index (zero-based) of the tab to jump to. Negative values are special /// the index (zero-based) of the tab to jump to. Negative values are special
/// values. /// values.
@ -77,3 +129,9 @@ pub const GotoTab = enum(c_int) {
last = -3, last = -3,
_, _,
}; };
pub const SecureInput = enum(c_int) {
on,
off,
toggle,
};

View File

@ -81,7 +81,7 @@ pub const App = struct {
/// Create a new split view. If the embedder doesn't support split /// Create a new split view. If the embedder doesn't support split
/// views then this can be null. /// views then this can be null.
new_split: ?*const fn (SurfaceUD, apprt.SplitDirection, apprt.Surface.Options) callconv(.C) void = null, new_split: ?*const fn (SurfaceUD, apprt.action.SplitDirection, apprt.Surface.Options) callconv(.C) void = null,
/// New tab with options. The surface may be null if there is no target /// New tab with options. The surface may be null if there is no target
/// surface in which case the apprt is expected to create a new window. /// surface in which case the apprt is expected to create a new window.
@ -98,10 +98,10 @@ pub const App = struct {
close_surface: ?*const fn (SurfaceUD, bool) callconv(.C) void = null, close_surface: ?*const fn (SurfaceUD, bool) callconv(.C) void = null,
/// Focus the previous/next split (if any). /// Focus the previous/next split (if any).
focus_split: ?*const fn (SurfaceUD, input.SplitFocusDirection) callconv(.C) void = null, focus_split: ?*const fn (SurfaceUD, apprt.action.GotoSplit) callconv(.C) void = null,
/// Resize the current split. /// Resize the current split.
resize_split: ?*const fn (SurfaceUD, input.SplitResizeDirection, u16) callconv(.C) void = null, resize_split: ?*const fn (SurfaceUD, apprt.action.ResizeSplit.Direction, u16) callconv(.C) void = null,
/// Equalize all splits in the current window /// Equalize all splits in the current window
equalize_splits: ?*const fn (SurfaceUD) callconv(.C) void = null, equalize_splits: ?*const fn (SurfaceUD) callconv(.C) void = null,
@ -534,16 +534,113 @@ pub const App = struct {
} }
} }
fn setPasswordInput(self: *App, target: apprt.Target, v: bool) void { fn newSplit(
const func = self.opts.set_password_input orelse { self: *const App,
log.info("runtime embedder does not support set_password_input", .{}); target: apprt.Target,
direction: apprt.action.SplitDirection,
) void {
const func = self.opts.new_split orelse {
log.info("runtime embedder does not support splits", .{});
return; return;
}; };
func(switch (target) { switch (target) {
.app => null, .app => func(null, direction, .{}),
.surface => |surface| surface.rt_surface.userdata, .surface => |v| func(
}, v); v.rt_surface.userdata,
direction,
v.rt_surface.newSurfaceOptions(),
),
}
}
fn gotoSplit(
self: *const App,
target: apprt.Target,
direction: apprt.action.GotoSplit,
) void {
const func = self.opts.focus_split orelse {
log.info("runtime embedder does not support focus split", .{});
return;
};
switch (target) {
.app => {},
.surface => |v| func(v.rt_surface.userdata, direction),
}
}
fn resizeSplit(
self: *const App,
target: apprt.Target,
resize: apprt.action.ResizeSplit,
) void {
const func = self.opts.resize_split orelse {
log.info("runtime embedder does not support resize split", .{});
return;
};
switch (target) {
.app => {},
.surface => |v| func(
v.rt_surface.userdata,
resize.direction,
resize.amount,
),
}
}
pub fn equalizeSplits(self: *const App, target: apprt.Target) void {
const func = self.opts.equalize_splits orelse {
log.info("runtime embedder does not support equalize splits", .{});
return;
};
switch (target) {
.app => func(null),
.surface => |v| func(v.rt_surface.userdata),
}
}
fn toggleSplitZoom(self: *const App, target: apprt.Target) void {
const func = self.opts.toggle_split_zoom orelse {
log.info("runtime embedder does not support split zoom", .{});
return;
};
switch (target) {
.app => func(null),
.surface => |v| func(v.rt_surface.userdata),
}
}
fn setPasswordInput(self: *App, target: apprt.Target, v: apprt.action.SecureInput) void {
switch (v) {
inline .on, .off => |tag| {
const func = self.opts.set_password_input orelse {
log.info("runtime embedder does not support set_password_input", .{});
return;
};
func(switch (target) {
.app => null,
.surface => |surface| surface.rt_surface.userdata,
}, switch (tag) {
.on => true,
.off => false,
else => comptime unreachable,
});
},
.toggle => {
const func = self.opts.toggle_secure_input orelse {
log.info("runtime embedder does not support toggle_secure_input", .{});
return;
};
func();
},
}
} }
/// Perform a given action. /// Perform a given action.
@ -561,11 +658,17 @@ pub const App = struct {
.new_tab => self.newTab(target), .new_tab => self.newTab(target),
.goto_tab => self.gotoTab(target, value), .goto_tab => self.gotoTab(target, value),
.new_split => self.newSplit(target, value),
.resize_split => self.resizeSplit(target, value),
.equalize_splits => self.equalizeSplits(target),
.toggle_split_zoom => self.toggleSplitZoom(target),
.goto_split => self.gotoSplit(target, value),
.open_config => try configpkg.edit.open(self.core_app.alloc), .open_config => try configpkg.edit.open(self.core_app.alloc),
.secure_input => self.setPasswordInput(target, value), .secure_input => self.setPasswordInput(target, value),
// Unimplemented // Unimplemented
.close_all_windows, .close_all_windows,
.toggle_window_decorations,
.quit_timer, .quit_timer,
=> log.warn("unimplemented action={}", .{action}), => log.warn("unimplemented action={}", .{action}),
} }
@ -795,16 +898,6 @@ pub const Surface = struct {
func(self.userdata, mode); func(self.userdata, mode);
} }
pub fn newSplit(self: *const Surface, direction: apprt.SplitDirection) !void {
const func = self.app.opts.new_split orelse {
log.info("runtime embedder does not support splits", .{});
return;
};
const options = self.newSurfaceOptions();
func(self.userdata, direction, options);
}
pub fn close(self: *const Surface, process_alive: bool) void { pub fn close(self: *const Surface, process_alive: bool) void {
const func = self.app.opts.close_surface orelse { const func = self.app.opts.close_surface orelse {
log.info("runtime embedder does not support closing a surface", .{}); log.info("runtime embedder does not support closing a surface", .{});
@ -814,42 +907,6 @@ pub const Surface = struct {
func(self.userdata, process_alive); func(self.userdata, process_alive);
} }
pub fn gotoSplit(self: *const Surface, direction: input.SplitFocusDirection) void {
const func = self.app.opts.focus_split orelse {
log.info("runtime embedder does not support focus split", .{});
return;
};
func(self.userdata, direction);
}
pub fn resizeSplit(self: *const Surface, direction: input.SplitResizeDirection, amount: u16) void {
const func = self.app.opts.resize_split orelse {
log.info("runtime embedder does not support resize split", .{});
return;
};
func(self.userdata, direction, amount);
}
pub fn equalizeSplits(self: *const Surface) void {
const func = self.app.opts.equalize_splits orelse {
log.info("runtime embedder does not support equalize splits", .{});
return;
};
func(self.userdata);
}
pub fn toggleSplitZoom(self: *const Surface) void {
const func = self.app.opts.toggle_split_zoom orelse {
log.info("runtime embedder does not support split zoom", .{});
return;
};
func(self.userdata);
}
pub fn getContentScale(self: *const Surface) !apprt.ContentScale { pub fn getContentScale(self: *const Surface) !apprt.ContentScale {
return self.content_scale; return self.content_scale;
} }
@ -1137,15 +1194,6 @@ pub const Surface = struct {
func(self.userdata, nonNativeFullscreen); func(self.userdata, nonNativeFullscreen);
} }
pub fn toggleSecureInput(self: *Surface) void {
const func = self.app.opts.toggle_secure_input orelse {
log.info("runtime embedder does not toggle_secure_input", .{});
return;
};
func();
}
fn newWindow(self: *const Surface) !void { fn newWindow(self: *const Surface) !void {
const func = self.app.opts.new_window orelse { const func = self.app.opts.new_window orelse {
log.info("runtime embedder does not support new_window", .{}); log.info("runtime embedder does not support new_window", .{});
@ -1899,26 +1947,61 @@ pub const CAPI = struct {
} }
/// Request that the surface split in the given direction. /// Request that the surface split in the given direction.
export fn ghostty_surface_split(ptr: *Surface, direction: apprt.SplitDirection) void { export fn ghostty_surface_split(ptr: *Surface, direction: apprt.action.SplitDirection) void {
ptr.newSplit(direction) catch {}; ptr.app.performAction(
.{ .surface = &ptr.core_surface },
.new_split,
direction,
) catch |err| {
log.err("error creating new split err={}", .{err});
return;
};
} }
/// Focus on the next split (if any). /// Focus on the next split (if any).
export fn ghostty_surface_split_focus(ptr: *Surface, direction: input.SplitFocusDirection) void { export fn ghostty_surface_split_focus(
ptr.gotoSplit(direction); 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});
return;
};
} }
/// Resize the current split by moving the split divider in the given /// Resize the current split by moving the split divider in the given
/// direction. `direction` specifies which direction the split divider will /// direction. `direction` specifies which direction the split divider will
/// move relative to the focused split. `amount` is a fractional value /// move relative to the focused split. `amount` is a fractional value
/// between 0 and 1 that specifies by how much the divider will move. /// between 0 and 1 that specifies by how much the divider will move.
export fn ghostty_surface_split_resize(ptr: *Surface, direction: input.SplitResizeDirection, amount: u16) void { export fn ghostty_surface_split_resize(
ptr.resizeSplit(direction, amount); ptr: *Surface,
direction: apprt.action.ResizeSplit.Direction,
amount: u16,
) void {
ptr.app.performAction(
.{ .surface = &ptr.core_surface },
.resize_split,
.{ .direction = direction, .amount = amount },
) catch |err| {
log.err("error resizing split err={}", .{err});
return;
};
} }
/// Equalize the size of all splits in the current window. /// Equalize the size of all splits in the current window.
export fn ghostty_surface_split_equalize(ptr: *Surface) void { export fn ghostty_surface_split_equalize(ptr: *Surface) void {
ptr.equalizeSplits(); ptr.app.performAction(
.{ .surface = &ptr.core_surface },
.equalize_splits,
{},
) catch |err| {
log.err("error equalizing splits err={}", .{err});
return;
};
} }
/// Invoke an action on the surface. /// Invoke an action on the surface.

View File

@ -150,7 +150,13 @@ pub const App = struct {
.open_config => try configpkg.edit.open(self.app.alloc), .open_config => try configpkg.edit.open(self.app.alloc),
// Unimplemented // Unimplemented
.new_split,
.goto_split,
.resize_split,
.equalize_splits,
.toggle_split_zoom,
.close_all_windows, .close_all_windows,
.toggle_window_decorations,
.goto_tab, .goto_tab,
.quit_timer, .quit_timer,
.secure_input, .secure_input,

View File

@ -62,13 +62,6 @@ pub const DesktopNotification = struct {
body: []const u8, body: []const u8,
}; };
// This is made extern (c_int) to make interop easier with our embedded
// runtime. The small size cost doesn't make a difference in our union.
pub const SplitDirection = enum(c_int) {
right,
down,
};
/// The color scheme in use (light vs dark). /// The color scheme in use (light vs dark).
pub const ColorScheme = enum(u2) { pub const ColorScheme = enum(u2) {
light = 0, light = 0,

View File

@ -410,8 +410,7 @@ pub const Action = union(enum) {
// Note: we don't support top or left yet // Note: we don't support top or left yet
}; };
// Extern because it is used in the embedded runtime ABI. pub const SplitFocusDirection = enum {
pub const SplitFocusDirection = enum(c_int) {
previous, previous,
next, next,
@ -421,8 +420,7 @@ pub const Action = union(enum) {
right, right,
}; };
// Extern because it is used in the embedded runtime ABI. pub const SplitResizeDirection = enum {
pub const SplitResizeDirection = enum(c_int) {
up, up,
down, down,
left, left,