Allow for default or inherited CWD in new window, tab and split surfaces (redone for GTK-NG) (#9158)

This commit is contained in:
Leah Amelia Chen
2026-01-07 20:45:06 +08:00
committed by GitHub
11 changed files with 93 additions and 22 deletions

View File

@@ -416,6 +416,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;
@@ -428,6 +434,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 {
@@ -1035,7 +1042,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);

View File

@@ -773,7 +773,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)),
]
)
@@ -810,7 +810,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)),
]
)
@@ -839,7 +839,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)),
]
)

View File

@@ -648,6 +648,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) {
@@ -669,6 +672,7 @@ extension Ghostty {
}
}
}
self.context = config.context
}
/// Provides a C-compatible ghostty configuration within a closure. The configuration
@@ -702,6 +706,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

View File

@@ -456,6 +456,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 {
@@ -477,7 +480,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.
@@ -894,14 +897,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,
};
}
@@ -1523,8 +1535,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.

View File

@@ -2238,8 +2238,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));

View File

@@ -219,7 +219,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);
}
}

View File

@@ -671,6 +671,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;
};
@@ -696,6 +699,7 @@ pub const Surface = extern struct {
pub fn setParent(
self: *Self,
parent: *CoreSurface,
context: apprt.surface.NewSurfaceContext,
) void {
const priv = self.private();
@@ -706,6 +710,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);
@@ -716,10 +723,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);
@@ -3206,6 +3211,7 @@ pub const Surface = extern struct {
var config = try apprt.surface.newConfig(
app.core(),
priv.config.?.get(),
priv.context,
);
defer config.deinit();

View File

@@ -161,8 +161,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);
}
}

View File

@@ -361,10 +361,14 @@ 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;
@@ -372,7 +376,9 @@ pub const Window = extern struct {
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 {
@@ -1231,7 +1237,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(

View File

@@ -159,12 +159,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);
@@ -175,7 +191,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;
}

View File

@@ -1845,11 +1845,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 tab. If no tab 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 split. If no split 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