mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
macos: support configuration via CLI arguments
This makes it so `zig build run` can take arguments such as `--config-default-files=false` or any other configuration. Previously, it only accepted commands such as `+version`. Incidentally, this also makes it so that the app in general can now take configuration arguments via the CLI if it is launched as a new instance via `open`. For example: open -n Ghostty.app --args --config-default-files=false This previously didn't work. This is kind of cool. To make this work, the libghostty C API was modified so that initialization requires the CLI args, and there is a new C API to try to execute an action if it was set.
This commit is contained in:
@ -778,8 +778,8 @@ typedef struct {
|
|||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
// Published API
|
// Published API
|
||||||
|
|
||||||
int ghostty_init(void);
|
int ghostty_init(uintptr_t, char**);
|
||||||
void ghostty_cli_main(uintptr_t, char**);
|
void ghostty_cli_try_action(void);
|
||||||
ghostty_info_s ghostty_info(void);
|
ghostty_info_s ghostty_info(void);
|
||||||
const char* ghostty_translate(const char*);
|
const char* ghostty_translate(const char*);
|
||||||
|
|
||||||
|
@ -48,8 +48,8 @@
|
|||||||
<string></string>
|
<string></string>
|
||||||
<key>LSEnvironment</key>
|
<key>LSEnvironment</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>GHOSTTY_MAC_APP</key>
|
<key>GHOSTTY_MAC_LAUNCH_SOURCE</key>
|
||||||
<string>1</string>
|
<string>app</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>MDItemKeywords</key>
|
<key>MDItemKeywords</key>
|
||||||
<string>Terminal</string>
|
<string>Terminal</string>
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; };
|
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; };
|
||||||
9351BE8E3D22937F003B3499 /* nvim in Resources */ = {isa = PBXBuildFile; fileRef = 9351BE8E2D22937F003B3499 /* nvim */; };
|
9351BE8E3D22937F003B3499 /* nvim in Resources */ = {isa = PBXBuildFile; fileRef = 9351BE8E2D22937F003B3499 /* nvim */; };
|
||||||
A50297352DFA0F3400B4E924 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50297342DFA0F3300B4E924 /* Double+Extension.swift */; };
|
A50297352DFA0F3400B4E924 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50297342DFA0F3300B4E924 /* Double+Extension.swift */; };
|
||||||
|
A505D21D2E1A2FA20018808F /* FileHandle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */; };
|
||||||
A511940F2E050595007258CC /* CloseTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A511940E2E050590007258CC /* CloseTerminalIntent.swift */; };
|
A511940F2E050595007258CC /* CloseTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A511940E2E050590007258CC /* CloseTerminalIntent.swift */; };
|
||||||
A51194112E05A483007258CC /* QuickTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194102E05A480007258CC /* QuickTerminalIntent.swift */; };
|
A51194112E05A483007258CC /* QuickTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194102E05A480007258CC /* QuickTerminalIntent.swift */; };
|
||||||
A51194132E05D006007258CC /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194122E05D003007258CC /* Optional+Extension.swift */; };
|
A51194132E05D006007258CC /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194122E05D003007258CC /* Optional+Extension.swift */; };
|
||||||
@ -158,6 +159,7 @@
|
|||||||
857F63802A5E64F200CA4815 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
857F63802A5E64F200CA4815 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
||||||
9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = "../zig-out/share/nvim"; sourceTree = "<group>"; };
|
9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = "../zig-out/share/nvim"; sourceTree = "<group>"; };
|
||||||
A50297342DFA0F3300B4E924 /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = "<group>"; };
|
A50297342DFA0F3300B4E924 /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = "<group>"; };
|
||||||
|
A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileHandle+Extension.swift"; sourceTree = "<group>"; };
|
||||||
A511940E2E050590007258CC /* CloseTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseTerminalIntent.swift; sourceTree = "<group>"; };
|
A511940E2E050590007258CC /* CloseTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseTerminalIntent.swift; sourceTree = "<group>"; };
|
||||||
A51194102E05A480007258CC /* QuickTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalIntent.swift; sourceTree = "<group>"; };
|
A51194102E05A480007258CC /* QuickTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalIntent.swift; sourceTree = "<group>"; };
|
||||||
A51194122E05D003007258CC /* Optional+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Extension.swift"; sourceTree = "<group>"; };
|
A51194122E05D003007258CC /* Optional+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Extension.swift"; sourceTree = "<group>"; };
|
||||||
@ -516,6 +518,7 @@
|
|||||||
A586366A2DF0A98900E04A10 /* Array+Extension.swift */,
|
A586366A2DF0A98900E04A10 /* Array+Extension.swift */,
|
||||||
A50297342DFA0F3300B4E924 /* Double+Extension.swift */,
|
A50297342DFA0F3300B4E924 /* Double+Extension.swift */,
|
||||||
A586366E2DF25D8300E04A10 /* Duration+Extension.swift */,
|
A586366E2DF25D8300E04A10 /* Duration+Extension.swift */,
|
||||||
|
A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */,
|
||||||
A53A29802DB44A5E00B6E02C /* KeyboardShortcut+Extension.swift */,
|
A53A29802DB44A5E00B6E02C /* KeyboardShortcut+Extension.swift */,
|
||||||
A53A297E2DB4480A00B6E02C /* EventModifiers+Extension.swift */,
|
A53A297E2DB4480A00B6E02C /* EventModifiers+Extension.swift */,
|
||||||
A51194122E05D003007258CC /* Optional+Extension.swift */,
|
A51194122E05D003007258CC /* Optional+Extension.swift */,
|
||||||
@ -799,6 +802,7 @@
|
|||||||
A5874D9D2DAD786100E83852 /* NSWindow+Extension.swift in Sources */,
|
A5874D9D2DAD786100E83852 /* NSWindow+Extension.swift in Sources */,
|
||||||
A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */,
|
A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */,
|
||||||
A58636732DF4813400E04A10 /* UndoManager+Extension.swift in Sources */,
|
A58636732DF4813400E04A10 /* UndoManager+Extension.swift in Sources */,
|
||||||
|
A505D21D2E1A2FA20018808F /* FileHandle+Extension.swift in Sources */,
|
||||||
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
|
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
|
||||||
CFBB5FEA2D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift in Sources */,
|
CFBB5FEA2D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift in Sources */,
|
||||||
A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */,
|
A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */,
|
||||||
|
@ -2,13 +2,32 @@ import AppKit
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import GhosttyKit
|
import GhosttyKit
|
||||||
|
|
||||||
// We put the GHOSTTY_MAC_APP env var into the Info.plist to detect
|
// Initialize Ghostty global state. We do this once right away because the
|
||||||
// whether we launch from the app or not. A user can fake this if
|
// CLI APIs require it and it lets us ensure it is done immediately for the
|
||||||
// they want but they're doing so at their own detriment...
|
// rest of the app.
|
||||||
let process = ProcessInfo.processInfo
|
if ghostty_init(UInt(CommandLine.argc), CommandLine.unsafeArgv) != GHOSTTY_SUCCESS {
|
||||||
if ((process.environment["GHOSTTY_MAC_APP"] ?? "") == "") {
|
Ghostty.logger.critical("ghostty_init failed")
|
||||||
ghostty_cli_main(UInt(CommandLine.argc), CommandLine.unsafeArgv)
|
|
||||||
|
// We also write to stderr if this is executed from the CLI or zig run
|
||||||
|
switch Ghostty.launchSource {
|
||||||
|
case .cli, .zig_run:
|
||||||
|
let stderrHandle = FileHandle.standardError
|
||||||
|
stderrHandle.write(
|
||||||
|
"Ghostty failed to initialize! If you're executing Ghostty from the command line\n" +
|
||||||
|
"then this is usually because an invalid action or multiple actions were specified.\n" +
|
||||||
|
"Actions start with the `+` character.\n\n" +
|
||||||
|
"View all available actions by running `ghostty +help`.\n")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
case .app:
|
||||||
|
// For the app we exit immediately. We should handle this case more
|
||||||
|
// gracefully in the future.
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will run the CLI action and exit if one was specified. A CLI
|
||||||
|
// action is a command starting with a `+`, such as `ghostty +boo`.
|
||||||
|
ghostty_cli_try_action();
|
||||||
|
|
||||||
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
|
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
|
||||||
|
@ -45,12 +45,6 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// Initialize ghostty global state. This happens once per process.
|
|
||||||
if ghostty_init() != GHOSTTY_SUCCESS {
|
|
||||||
logger.critical("ghostty_init failed, weird things may happen")
|
|
||||||
readiness = .error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the global configuration.
|
// Initialize the global configuration.
|
||||||
self.config = Config()
|
self.config = Config()
|
||||||
if self.config.config == nil {
|
if self.config.config == nil {
|
||||||
|
@ -61,9 +61,12 @@ extension Ghostty {
|
|||||||
/// its up to the env var being set in the correct circumstance.
|
/// its up to the env var being set in the correct circumstance.
|
||||||
static var launchSource: LaunchSource {
|
static var launchSource: LaunchSource {
|
||||||
guard let envValue = ProcessInfo.processInfo.environment["GHOSTTY_MAC_LAUNCH_SOURCE"] else {
|
guard let envValue = ProcessInfo.processInfo.environment["GHOSTTY_MAC_LAUNCH_SOURCE"] else {
|
||||||
return .app
|
// We default to the CLI because the app bundle always sets the
|
||||||
|
// source. If its unset we assume we're in a CLI environment.
|
||||||
|
return .cli
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the env var is set but its unknown then we default back to the app.
|
||||||
return LaunchSource(rawValue: envValue) ?? .app
|
return LaunchSource(rawValue: envValue) ?? .app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension FileHandle: @retroactive TextOutputStream {
|
||||||
|
/// Write a string to a filehandle.
|
||||||
|
public func write(_ string: String) {
|
||||||
|
let data = Data(string.utf8)
|
||||||
|
self.write(data)
|
||||||
|
}
|
||||||
|
}
|
@ -884,7 +884,7 @@ pub const Surface = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove this so that running `ghostty` within Ghostty works.
|
// Remove this so that running `ghostty` within Ghostty works.
|
||||||
env.remove("GHOSTTY_MAC_APP");
|
env.remove("GHOSTTY_MAC_LAUNCH_SOURCE");
|
||||||
|
|
||||||
// If we were launched from the desktop then we want to
|
// If we were launched from the desktop then we want to
|
||||||
// remove the LANGUAGE env var so that we don't inherit
|
// remove the LANGUAGE env var so that we don't inherit
|
||||||
|
@ -122,10 +122,6 @@ pub fn init(
|
|||||||
|
|
||||||
if (b.args) |args| {
|
if (b.args) |args| {
|
||||||
open.addArgs(args);
|
open.addArgs(args);
|
||||||
} else {
|
|
||||||
// This tricks the app into thinking it's running from the
|
|
||||||
// app bundle so we don't execute our CLI mode.
|
|
||||||
open.setEnvironmentVariable("GHOSTTY_MAC_APP", "1");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break :open open;
|
break :open open;
|
||||||
|
@ -46,17 +46,11 @@ const Info = extern struct {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Initialize ghostty global state. It is possible to have more than
|
/// Initialize ghostty global state.
|
||||||
/// one global state but it has zero practical benefit.
|
export fn ghostty_init(argc: usize, argv: [*][*:0]u8) c_int {
|
||||||
export fn ghostty_init() c_int {
|
|
||||||
assert(builtin.link_libc);
|
assert(builtin.link_libc);
|
||||||
|
|
||||||
// Since in the lib we don't go through start.zig, we need
|
std.os.argv = argv[0..argc];
|
||||||
// to populate argv so that inspecting std.os.argv doesn't
|
|
||||||
// touch uninitialized memory.
|
|
||||||
var argv: [0][*:0]u8 = .{};
|
|
||||||
std.os.argv = &argv;
|
|
||||||
|
|
||||||
state.init() catch |err| {
|
state.init() catch |err| {
|
||||||
std.log.err("failed to initialize ghostty error={}", .{err});
|
std.log.err("failed to initialize ghostty error={}", .{err});
|
||||||
return 1;
|
return 1;
|
||||||
@ -65,15 +59,17 @@ export fn ghostty_init() c_int {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is the entrypoint for the CLI version of Ghostty. This
|
/// Runs an action if it is specified. If there is no action this returns
|
||||||
/// is mutually exclusive to ghostty_init. Do NOT run ghostty_init
|
/// false. If there is an action then this doesn't return.
|
||||||
/// if you are going to run this. This will not return.
|
export fn ghostty_cli_try_action() void {
|
||||||
export fn ghostty_cli_main(argc: usize, argv: [*][*:0]u8) noreturn {
|
const action = state.action orelse return;
|
||||||
std.os.argv = argv[0..argc];
|
std.log.info("executing CLI action={}", .{action});
|
||||||
main.main() catch |err| {
|
posix.exit(action.run(state.alloc) catch |err| {
|
||||||
std.log.err("failed to run ghostty error={}", .{err});
|
std.log.err("CLI action failed error={}", .{err});
|
||||||
posix.exit(1);
|
posix.exit(1);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
posix.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return metadata about Ghostty, such as version, build mode, etc.
|
/// Return metadata about Ghostty, such as version, build mode, etc.
|
||||||
|
@ -24,8 +24,15 @@ pub fn launchedFromDesktop() bool {
|
|||||||
// This special case is so that if we launch the app via the
|
// This special case is so that if we launch the app via the
|
||||||
// app bundle (i.e. via open) then we still treat it as if it
|
// app bundle (i.e. via open) then we still treat it as if it
|
||||||
// was launched from the desktop.
|
// was launched from the desktop.
|
||||||
if (build_config.artifact == .lib and
|
if (build_config.artifact == .lib) lib: {
|
||||||
posix.getenv("GHOSTTY_MAC_APP") != null) break :macos true;
|
const env = "GHOSTTY_MAC_LAUNCH_SOURCE";
|
||||||
|
const source = posix.getenv(env) orelse break :lib;
|
||||||
|
|
||||||
|
// Source can be "app", "cli", or "zig_run". We assume
|
||||||
|
// its the desktop only if its "app". We may want to do
|
||||||
|
// "zig_run" but at the moment there's no reason.
|
||||||
|
if (std.mem.eql(u8, source, "app")) break :macos true;
|
||||||
|
}
|
||||||
|
|
||||||
break :macos c.getppid() == 1;
|
break :macos c.getppid() == 1;
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user