diff --git a/include/ghostty.h b/include/ghostty.h index acb6988d67..98cfbcaa43 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -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; @@ -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 { @@ -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); diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 3db8e7a112..1f77dc06e0 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -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)), ] ) @@ -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)), ] ) @@ -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)), ] ) diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index c650bdf8f1..f370884d72 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -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) { @@ -464,6 +467,7 @@ extension Ghostty { } } } + self.context = config.context } /// Provides a C-compatible ghostty configuration within a closure. The configuration @@ -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 diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 6175579957..49c321a2da 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -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 { @@ -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. @@ -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, }; } @@ -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. diff --git a/src/apprt/gtk/class/application.zig b/src/apprt/gtk/class/application.zig index ceea6fee5e..4b7fe0217d 100644 --- a/src/apprt/gtk/class/application.zig +++ b/src/apprt/gtk/class/application.zig @@ -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)); diff --git a/src/apprt/gtk/class/split_tree.zig b/src/apprt/gtk/class/split_tree.zig index 1c901b1bb5..ba439fd7a8 100644 --- a/src/apprt/gtk/class/split_tree.zig +++ b/src/apprt/gtk/class/split_tree.zig @@ -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); } } diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 646ad5dbdc..a6becd5c53 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -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; }; @@ -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(); @@ -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); @@ -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); @@ -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 diff --git a/src/apprt/gtk/class/tab.zig b/src/apprt/gtk/class/tab.zig index c9928be8bb..ac52637d47 100644 --- a/src/apprt/gtk/class/tab.zig +++ b/src/apprt/gtk/class/tab.zig @@ -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); } } diff --git a/src/apprt/gtk/class/window.zig b/src/apprt/gtk/class/window.zig index 4febebfc69..07f3339867 100644 --- a/src/apprt/gtk/class/window.zig +++ b/src/apprt/gtk/class/window.zig @@ -362,10 +362,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; @@ -373,7 +377,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 { @@ -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( diff --git a/src/apprt/surface.zig b/src/apprt/surface.zig index b71bf1e6ea..c90d819a3c 100644 --- a/src/apprt/surface.zig +++ b/src/apprt/surface.zig @@ -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); @@ -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; } diff --git a/src/config/Config.zig b/src/config/Config.zig index c9ae121e41..3dd5543755 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -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