Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion include/ghostty.h
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,12 @@ typedef union {
ghostty_platform_ios_s ios;
} ghostty_platform_u;

typedef enum {
GHOSTTY_SURFACE_CONTEXT_WINDOW = 0,
GHOSTTY_SURFACE_CONTEXT_TAB = 1,
GHOSTTY_SURFACE_CONTEXT_SPLIT = 2,
} ghostty_surface_context_e;

typedef struct {
ghostty_platform_e platform_tag;
ghostty_platform_u platform;
Expand All @@ -421,6 +427,7 @@ typedef struct {
size_t env_var_count;
const char* initial_input;
bool wait_after_command;
ghostty_surface_context_e context;
} ghostty_surface_config_s;

typedef struct {
Expand Down Expand Up @@ -950,7 +957,7 @@ ghostty_surface_t ghostty_surface_new(ghostty_app_t,
void ghostty_surface_free(ghostty_surface_t);
void* ghostty_surface_userdata(ghostty_surface_t);
ghostty_app_t ghostty_surface_app(ghostty_surface_t);
ghostty_surface_config_s ghostty_surface_inherited_config(ghostty_surface_t);
ghostty_surface_config_s ghostty_surface_inherited_config(ghostty_surface_t, ghostty_surface_context_e);
void ghostty_surface_update_config(ghostty_surface_t, ghostty_config_t);
bool ghostty_surface_needs_confirm_quit(ghostty_surface_t);
bool ghostty_surface_process_exited(ghostty_surface_t);
Expand Down
6 changes: 3 additions & 3 deletions macos/Sources/Ghostty/Ghostty.App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ extension Ghostty {
name: Notification.ghosttyNewWindow,
object: surfaceView,
userInfo: [
Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface)),
Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface, GHOSTTY_SURFACE_CONTEXT_WINDOW)),
]
)

Expand Down Expand Up @@ -751,7 +751,7 @@ extension Ghostty {
name: Notification.ghosttyNewTab,
object: surfaceView,
userInfo: [
Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface)),
Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface, GHOSTTY_SURFACE_CONTEXT_TAB)),
]
)

Expand Down Expand Up @@ -780,7 +780,7 @@ extension Ghostty {
object: surfaceView,
userInfo: [
"direction": direction,
Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface)),
Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface, GHOSTTY_SURFACE_CONTEXT_SPLIT)),
]
)

Expand Down
7 changes: 7 additions & 0 deletions macos/Sources/Ghostty/SurfaceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,9 @@ extension Ghostty {
/// Wait after the command
var waitAfterCommand: Bool = false

/// Context for surface creation
var context: ghostty_surface_context_e = GHOSTTY_SURFACE_CONTEXT_WINDOW

init() {}

init(from config: ghostty_surface_config_s) {
Expand All @@ -464,6 +467,7 @@ extension Ghostty {
}
}
}
self.context = config.context
}

/// Provides a C-compatible ghostty configuration within a closure. The configuration
Expand Down Expand Up @@ -497,6 +501,9 @@ extension Ghostty {
// Set wait after command
config.wait_after_command = waitAfterCommand

// Set context
config.context = context

// Use withCString to ensure strings remain valid for the duration of the closure
return try workingDirectory.withCString { cWorkingDir in
config.working_directory = cWorkingDir
Expand Down
23 changes: 19 additions & 4 deletions src/apprt/embedded.zig
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,9 @@ pub const Surface = struct {

/// Wait after the command exits
wait_after_command: bool = false,

/// Context for the new surface
context: apprt.surface.NewSurfaceContext = .window,
};

pub fn init(self: *Surface, app: *App, opts: Options) !void {
Expand All @@ -471,7 +474,7 @@ pub const Surface = struct {
errdefer app.core_app.deleteSurface(self);

// Shallow copy the config so that we can modify it.
var config = try apprt.surface.newConfig(app.core_app, &app.config);
var config = try apprt.surface.newConfig(app.core_app, &app.config, opts.context);
defer config.deinit();

// If we have a working directory from the options then we set it.
Expand Down Expand Up @@ -873,14 +876,23 @@ pub const Surface = struct {
};
}

pub fn newSurfaceOptions(self: *const Surface) apprt.Surface.Options {
pub fn newSurfaceOptions(self: *const Surface, context: apprt.surface.NewSurfaceContext) apprt.Surface.Options {
const font_size: f32 = font_size: {
if (!self.app.config.@"window-inherit-font-size") break :font_size 0;
break :font_size self.core_surface.font_size.points;
};

const working_directory: ?[*:0]const u8 = wd: {
if (!apprt.surface.shouldInheritWorkingDirectory(context, &self.app.config)) break :wd null;
const cwd = self.core_surface.pwd(self.app.core_app.alloc) catch null orelse break :wd null;
defer self.app.core_app.alloc.free(cwd);
break :wd self.app.core_app.alloc.dupeZ(u8, cwd) catch null;
};

return .{
.font_size = font_size,
.working_directory = working_directory,
.context = context,
};
}

Expand Down Expand Up @@ -1494,8 +1506,11 @@ pub const CAPI = struct {
}

/// Returns the config to use for surfaces that inherit from this one.
export fn ghostty_surface_inherited_config(surface: *Surface) Surface.Options {
return surface.newSurfaceOptions();
export fn ghostty_surface_inherited_config(
surface: *Surface,
source: apprt.surface.NewSurfaceContext,
) Surface.Options {
return surface.newSurfaceOptions(source);
}

/// Update the configuration to the provided config for only this surface.
Expand Down
4 changes: 2 additions & 2 deletions src/apprt/gtk/class/application.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2144,8 +2144,8 @@ const Action = struct {
.{},
);

// Create a new tab
win.newTab(parent);
// Create a new tab with window context (first tab in new window)
win.newTabForWindow(parent);

// Show the window
gtk.Window.present(win.as(gtk.Window));
Expand Down
2 changes: 1 addition & 1 deletion src/apprt/gtk/class/split_tree.zig
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ pub const SplitTree = extern struct {
// Inherit properly if we were asked to.
if (parent_) |p| {
if (p.core()) |core| {
surface.setParent(core);
surface.setParent(core, .split);
}
}

Expand Down
18 changes: 10 additions & 8 deletions src/apprt/gtk/class/surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,9 @@ pub const Surface = extern struct {
error_page: *adw.StatusPage,
terminal_page: *gtk.Overlay,

/// The context for this surface (window, tab, or split)
context: apprt.surface.NewSurfaceContext = .window,

pub var offset: c_int = 0;
};

Expand All @@ -648,6 +651,7 @@ pub const Surface = extern struct {
pub fn setParent(
self: *Self,
parent: *CoreSurface,
context: apprt.surface.NewSurfaceContext,
) void {
const priv = self.private();

Expand All @@ -658,6 +662,9 @@ pub const Surface = extern struct {
return;
}

// Store the context so initSurface can use it
priv.context = context;

// Setup our font size
const font_size_ptr = glib.ext.create(font.face.DesiredSize);
errdefer glib.ext.destroy(font_size_ptr);
Expand All @@ -668,10 +675,8 @@ pub const Surface = extern struct {
// Remainder needs a config. If there is no config we just assume
// we aren't inheriting any of these values.
if (priv.config) |config_obj| {
const config = config_obj.get();

// Setup our pwd if configured to inherit
if (config.@"window-inherit-working-directory") {
// Setup our cwd if configured to inherit
if (apprt.surface.shouldInheritWorkingDirectory(context, config_obj.get())) {
if (parent.rt_surface.surface.getPwd()) |pwd| {
priv.pwd = glib.ext.dupeZ(u8, pwd);
self.as(gobject.Object).notifyByPspec(properties.pwd.impl.param_spec);
Expand Down Expand Up @@ -3043,10 +3048,7 @@ pub const Surface = extern struct {
errdefer app.core().deleteSurface(self.rt());

// Initialize our surface configuration.
var config = try apprt.surface.newConfig(
app.core(),
priv.config.?.get(),
);
var config = try apprt.surface.newConfig(app.core(), priv.config.?.get(), priv.context);
defer config.deinit();

// Properties that can impact surface init
Expand Down
6 changes: 5 additions & 1 deletion src/apprt/gtk/class/tab.zig
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,12 @@ pub const Tab = extern struct {
/// ever created for a tab. If a surface was already created this does
/// nothing.
pub fn setParent(self: *Self, parent: *CoreSurface) void {
self.setParentWithContext(parent, .tab);
}

pub fn setParentWithContext(self: *Self, parent: *CoreSurface, context: apprt.surface.NewSurfaceContext) void {
if (self.getActiveSurface()) |surface| {
surface.setParent(parent);
surface.setParent(parent, context);
}
}

Expand Down
14 changes: 10 additions & 4 deletions src/apprt/gtk/class/window.zig
Original file line number Diff line number Diff line change
Expand Up @@ -362,18 +362,24 @@ pub const Window = extern struct {
/// at the position dictated by the `window-new-tab-position` config.
/// The new tab will be selected.
pub fn newTab(self: *Self, parent_: ?*CoreSurface) void {
_ = self.newTabPage(parent_);
_ = self.newTabPage(parent_, .tab);
}

fn newTabPage(self: *Self, parent_: ?*CoreSurface) *adw.TabPage {
pub fn newTabForWindow(self: *Self, parent_: ?*CoreSurface) void {
_ = self.newTabPage(parent_, .window);
}

fn newTabPage(self: *Self, parent_: ?*CoreSurface, context: apprt.surface.NewSurfaceContext) *adw.TabPage {
const priv = self.private();
const tab_view = priv.tab_view;

// Create our new tab object
const tab = gobject.ext.newInstance(Tab, .{
.config = priv.config,
});
if (parent_) |p| tab.setParent(p);
if (parent_) |p| {
tab.setParentWithContext(p, context);
}

// Get the position that we should insert the new tab at.
const config = if (priv.config) |v| v.get() else {
Expand Down Expand Up @@ -1232,7 +1238,7 @@ pub const Window = extern struct {
_: *adw.TabOverview,
self: *Self,
) callconv(.c) *adw.TabPage {
return self.newTabPage(if (self.getActiveSurface()) |v| v.core() else null);
return self.newTabPage(if (self.getActiveSurface()) |v| v.core() else null, .tab);
}

fn tabOverviewOpen(
Expand Down
18 changes: 17 additions & 1 deletion src/apprt/surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,28 @@ pub const Mailbox = struct {
}
};

/// Context for new surface creation to determine inheritance behavior
pub const NewSurfaceContext = enum(c_int) {
window = 0,
tab = 1,
split = 2,
};

pub fn shouldInheritWorkingDirectory(context: NewSurfaceContext, config: *const Config) bool {
return switch (context) {
.window => config.@"window-inherit-working-directory",
.tab => config.@"tab-inherit-working-directory",
.split => config.@"split-inherit-working-directory",
};
}

/// Returns a new config for a surface for the given app that should be
/// used for any new surfaces. The resulting config should be deinitialized
/// after the surface is initialized.
pub fn newConfig(
app: *const App,
config: *const Config,
context: NewSurfaceContext,
) Allocator.Error!Config {
// Create a shallow clone
var copy = config.shallowClone(app.alloc);
Expand All @@ -169,7 +185,7 @@ pub fn newConfig(
// Get our previously focused surface for some inherited values.
const prev = app.focusedSurface();
if (prev) |p| {
if (config.@"window-inherit-working-directory") {
if (shouldInheritWorkingDirectory(context, config)) {
if (try p.pwd(alloc)) |pwd| {
copy.@"working-directory" = pwd;
}
Expand Down
12 changes: 11 additions & 1 deletion src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1667,11 +1667,21 @@ keybind: Keybinds = .{},
/// This setting is only supported currently on macOS.
@"window-vsync": bool = true,

/// If true, new windows and tabs will inherit the working directory of the
/// If true, new windows will inherit the working directory of the
/// previously focused window. If no window was previously focused, the default
/// working directory will be used (the `working-directory` option).
@"window-inherit-working-directory": bool = true,

/// If true, new tabs will inherit the working directory of the
/// previously focused window. If no window was previously focused, the default
/// working directory will be used (the `working-directory` option).
@"tab-inherit-working-directory": bool = true,

/// If true, new split panes will inherit the working directory of the
/// previously focused window. If no window was previously focused, the default
/// working directory will be used (the `working-directory` option).
@"split-inherit-working-directory": bool = true,

/// If true, new windows and tabs will inherit the font size of the previously
/// focused window. If no window was previously focused, the default font size
/// will be used. If this is false, the default font size specified in the
Expand Down