diff --git a/include/ghostty.h b/include/ghostty.h index 0c5a63448..181f7b7f8 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -413,6 +413,7 @@ typedef struct { const char* command; ghostty_env_var_s* env_vars; size_t env_var_count; + const char* initial_input; } ghostty_surface_config_s; typedef struct { diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 4ffb9efa4..734fcbc20 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -384,10 +384,17 @@ class AppDelegate: NSObject, config.workingDirectory = filename _ = TerminalController.newTab(ghostty, withBaseConfig: config) } else { - // When opening a file, open a new window with that file as the command, - // and its parent directory as the working directory. - config.command = filename + // When opening a file, we want to execute the file. To do this, we + // don't override the command directly, because it won't load the + // profile/rc files for the shell, which is super important on macOS + // due to things like Homebrew. Instead, we set the command to + // `; exit` which is what Terminal and iTerm2 do. + config.initialInput = "\(filename); exit\n" + + // Set the parent directory to our working directory so that relative + // paths in scripts work. config.workingDirectory = (filename as NSString).deletingLastPathComponent + _ = TerminalController.newWindow(ghostty, withBaseConfig: config) } diff --git a/macos/Sources/Features/App Intents/IntentPermission.swift b/macos/Sources/Features/App Intents/IntentPermission.swift index 2ec4f2bd9..210d2cb2e 100644 --- a/macos/Sources/Features/App Intents/IntentPermission.swift +++ b/macos/Sources/Features/App Intents/IntentPermission.swift @@ -45,7 +45,7 @@ func requestIntentPermission() async -> Bool { PermissionRequest.show( - "org.mitchellh.ghostty.shortcutsPermission", + "com.mitchellh.ghostty.shortcutsPermission", message: "Allow Shortcuts to interact with Ghostty?", allowDuration: .forever, rememberDuration: nil, diff --git a/macos/Sources/Features/App Intents/NewTerminalIntent.swift b/macos/Sources/Features/App Intents/NewTerminalIntent.swift index 3c36bed87..9b95208bb 100644 --- a/macos/Sources/Features/App Intents/NewTerminalIntent.swift +++ b/macos/Sources/Features/App Intents/NewTerminalIntent.swift @@ -19,7 +19,7 @@ struct NewTerminalIntent: AppIntent { @Parameter( title: "Command", - description: "Command to execute instead of the default shell." + description: "Command to execute within your configured shell.", ) var command: String? @@ -60,7 +60,12 @@ struct NewTerminalIntent: AppIntent { let ghostty = appDelegate.ghostty var config = Ghostty.SurfaceConfiguration() - config.command = command + + // We don't run command as "command" and instead use "initialInput" so + // that we can get all the login scripts to setup things like PATH. + if let command { + config.initialInput = "\(command); exit\n" + } // If we were given a working directory then open that directory if let url = workingDirectory?.fileURL { diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 2f0623b79..aa4de5178 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -422,6 +422,9 @@ extension Ghostty { /// Environment variables to set for the terminal var environmentVariables: [String: String] = [:] + /// Extra input to send as stdin + var initialInput: String? = nil + init() {} init(from config: ghostty_surface_config_s) { @@ -450,13 +453,13 @@ extension Ghostty { func withCValue(view: SurfaceView, _ body: (inout ghostty_surface_config_s) throws -> T) rethrows -> T { var config = ghostty_surface_config_new() config.userdata = Unmanaged.passUnretained(view).toOpaque() - #if os(macOS) +#if os(macOS) config.platform_tag = GHOSTTY_PLATFORM_MACOS config.platform = ghostty_platform_u(macos: ghostty_platform_macos_s( nsview: Unmanaged.passUnretained(view).toOpaque() )) config.scale_factor = NSScreen.main!.backingScaleFactor - #elseif os(iOS) +#elseif os(iOS) config.platform_tag = GHOSTTY_PLATFORM_IOS config.platform = ghostty_platform_u(ios: ghostty_platform_ios_s( uiview: Unmanaged.passUnretained(view).toOpaque() @@ -466,9 +469,9 @@ extension Ghostty { // probably set this to some default, then modify the scale factor through // libghostty APIs when a UIView is attached to a window/scene. TODO. config.scale_factor = UIScreen.main.scale - #else - #error("unsupported target") - #endif +#else +#error("unsupported target") +#endif // Zero is our default value that means to inherit the font size. config.font_size = fontSize ?? 0 @@ -480,27 +483,31 @@ extension Ghostty { return try command.withCString { cCommand in config.command = cCommand - // Convert dictionary to arrays for easier processing - let keys = Array(environmentVariables.keys) - let values = Array(environmentVariables.values) + return try initialInput.withCString { cInput in + config.initial_input = cInput - // Create C strings for all keys and values - return try keys.withCStrings { keyCStrings in - return try values.withCStrings { valueCStrings in - // Create array of ghostty_env_var_s - var envVars = Array() - envVars.reserveCapacity(environmentVariables.count) - for i in 0..() + envVars.reserveCapacity(environmentVariables.count) + for i in 0..