From 65f1cefb4e8fb2da369d8afdcf2ea6e15ffde164 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 5 Nov 2024 16:51:13 -0800 Subject: [PATCH] config: add "initial-command" config, "-e" sets that Fixes #2601 It is more expected behavior that `-e` affects only the first window. By introducing a dedicated configuration we avoid making `-e` too magical: its simply syntax sugar for setting the "initial-command" configuration. --- src/App.zig | 5 +++++ src/Surface.zig | 17 +++++++++++++---- src/config/Config.zig | 31 +++++++++++++++++++++++++------ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/App.zig b/src/App.zig index c54c67167..cc8277c52 100644 --- a/src/App.zig +++ b/src/App.zig @@ -66,6 +66,11 @@ font_grid_set: font.SharedGridSet, last_notification_time: ?std.time.Instant = null, last_notification_digest: u64 = 0, +/// Set to false once we've created at least one surface. This +/// never goes true again. This can be used by surfaces to determine +/// if they are the first surface. +first: bool = true, + pub const CreateError = Allocator.Error || font.SharedGridSet.InitError; /// Initialize the main app instance. This creates the main window, sets diff --git a/src/Surface.zig b/src/Surface.zig index d19f9e812..d0c199010 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -474,13 +474,19 @@ pub fn init( .config = derived_config, }; + // The command we're going to execute + const command: ?[]const u8 = if (app.first) + config.@"initial-command" orelse config.command + else + config.command; + // Start our IO implementation // This separate block ({}) is important because our errdefers must // be scoped here to be valid. { // Initialize our IO backend var io_exec = try termio.Exec.init(alloc, .{ - .command = config.command, + .command = command, .shell_integration = config.@"shell-integration", .shell_integration_features = config.@"shell-integration-features", .working_directory = config.@"working-directory", @@ -618,9 +624,9 @@ pub fn init( // For xdg-terminal-exec execution we special-case and set the window // title to the command being executed. This allows window managers // to set custom styling based on the command being executed. - const command = config.command orelse break :xdg; - if (command.len > 0) { - const title = alloc.dupeZ(u8, command) catch |err| { + const v = command orelse break :xdg; + if (v.len > 0) { + const title = alloc.dupeZ(u8, v) catch |err| { log.warn( "error copying command for title, title will not be set err={}", .{err}, @@ -635,6 +641,9 @@ pub fn init( ); } } + + // We are no longer the first surface + app.first = false; } pub fn deinit(self: *Surface) void { diff --git a/src/config/Config.zig b/src/config/Config.zig index a674046e1..e6b9d35ab 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -513,7 +513,26 @@ palette: Palette = .{}, /// arguments are provided, the command will be executed using `/bin/sh -c`. /// Ghostty does not do any shell command parsing. /// -/// If you're using the `ghostty` CLI there is also a shortcut to run a command +/// This command will be used for all new terminal surfaces, i.e. new windows, +/// tabs, etc. If you want to run a command only for the first terminal surface +/// created when Ghostty starts, use the `initial-command` configuration. +/// +/// Ghostty supports the common `-e` flag for executing a command with +/// arguments. For example, `ghostty -e fish --with --custom --args`. +/// This flag sets the `initial-command` configuration, see that for more +/// information. +command: ?[]const u8 = null, + +/// This is the same as "command", but only applies to the first terminal +/// surface created when Ghostty starts. Subsequent terminal surfaces will use +/// the `command` configuration. +/// +/// After the first terminal surface is created (or closed), there is no +/// way to run this initial command again automatically. As such, setting +/// this at runtime works but will only affect the next terminal surface +/// if it is the first one ever created. +/// +/// If you're using the `ghostty` CLI there is also a shortcut to set this /// with arguments directly: you can use the `-e` flag. For example: `ghostty -e /// fish --with --custom --args`. The `-e` flag automatically forces some /// other behaviors as well: @@ -525,7 +544,7 @@ palette: Palette = .{}, /// process will exit when the command exits. Additionally, the /// `quit-after-last-window-closed-delay` is unset. /// -command: ?[]const u8 = null, +@"initial-command": ?[]const u8 = null, /// If true, keep the terminal open after the command exits. Normally, the /// terminal window closes when the running command (such as a shell) exits. @@ -2356,7 +2375,7 @@ pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void { } self.@"_xdg-terminal-exec" = true; - self.command = command.items[0 .. command.items.len - 1]; + self.@"initial-command" = command.items[0 .. command.items.len - 1]; return; } } @@ -2755,7 +2774,7 @@ pub fn parseManuallyHook( return false; } - self.command = command.items[0 .. command.items.len - 1]; + self.@"initial-command" = command.items[0 .. command.items.len - 1]; // See "command" docs for the implied configurations and why. self.@"gtk-single-instance" = .false; @@ -2945,7 +2964,7 @@ test "parse e: command only" { var it: TestIterator = .{ .data = &.{"foo"} }; try testing.expect(!try cfg.parseManuallyHook(alloc, "-e", &it)); - try testing.expectEqualStrings("foo", cfg.command.?); + try testing.expectEqualStrings("foo", cfg.@"initial-command".?); } test "parse e: command and args" { @@ -2956,7 +2975,7 @@ test "parse e: command and args" { var it: TestIterator = .{ .data = &.{ "echo", "foo", "bar baz" } }; try testing.expect(!try cfg.parseManuallyHook(alloc, "-e", &it)); - try testing.expectEqualStrings("echo foo bar baz", cfg.command.?); + try testing.expectEqualStrings("echo foo bar baz", cfg.@"initial-command".?); } test "clone default" {