mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 07:46:12 +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
|
||||
|
||||
int ghostty_init(void);
|
||||
void ghostty_cli_main(uintptr_t, char**);
|
||||
int ghostty_init(uintptr_t, char**);
|
||||
void ghostty_cli_try_action(void);
|
||||
ghostty_info_s ghostty_info(void);
|
||||
const char* ghostty_translate(const char*);
|
||||
|
||||
|
@ -48,8 +48,8 @@
|
||||
<string></string>
|
||||
<key>LSEnvironment</key>
|
||||
<dict>
|
||||
<key>GHOSTTY_MAC_APP</key>
|
||||
<string>1</string>
|
||||
<key>GHOSTTY_MAC_LAUNCH_SOURCE</key>
|
||||
<string>app</string>
|
||||
</dict>
|
||||
<key>MDItemKeywords</key>
|
||||
<string>Terminal</string>
|
||||
|
@ -13,6 +13,7 @@
|
||||
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; };
|
||||
9351BE8E3D22937F003B3499 /* nvim in Resources */ = {isa = PBXBuildFile; fileRef = 9351BE8E2D22937F003B3499 /* nvim */; };
|
||||
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 */; };
|
||||
A51194112E05A483007258CC /* QuickTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194102E05A480007258CC /* QuickTerminalIntent.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -516,6 +518,7 @@
|
||||
A586366A2DF0A98900E04A10 /* Array+Extension.swift */,
|
||||
A50297342DFA0F3300B4E924 /* Double+Extension.swift */,
|
||||
A586366E2DF25D8300E04A10 /* Duration+Extension.swift */,
|
||||
A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */,
|
||||
A53A29802DB44A5E00B6E02C /* KeyboardShortcut+Extension.swift */,
|
||||
A53A297E2DB4480A00B6E02C /* EventModifiers+Extension.swift */,
|
||||
A51194122E05D003007258CC /* Optional+Extension.swift */,
|
||||
@ -799,6 +802,7 @@
|
||||
A5874D9D2DAD786100E83852 /* NSWindow+Extension.swift in Sources */,
|
||||
A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */,
|
||||
A58636732DF4813400E04A10 /* UndoManager+Extension.swift in Sources */,
|
||||
A505D21D2E1A2FA20018808F /* FileHandle+Extension.swift in Sources */,
|
||||
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
|
||||
CFBB5FEA2D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift in Sources */,
|
||||
A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */,
|
||||
|
@ -2,13 +2,32 @@ import AppKit
|
||||
import Cocoa
|
||||
import GhosttyKit
|
||||
|
||||
// We put the GHOSTTY_MAC_APP env var into the Info.plist to detect
|
||||
// whether we launch from the app or not. A user can fake this if
|
||||
// they want but they're doing so at their own detriment...
|
||||
let process = ProcessInfo.processInfo
|
||||
if ((process.environment["GHOSTTY_MAC_APP"] ?? "") == "") {
|
||||
ghostty_cli_main(UInt(CommandLine.argc), CommandLine.unsafeArgv)
|
||||
exit(1)
|
||||
// Initialize Ghostty global state. We do this once right away because the
|
||||
// CLI APIs require it and it lets us ensure it is done immediately for the
|
||||
// rest of the app.
|
||||
if ghostty_init(UInt(CommandLine.argc), CommandLine.unsafeArgv) != GHOSTTY_SUCCESS {
|
||||
Ghostty.logger.critical("ghostty_init failed")
|
||||
|
||||
// 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)
|
||||
|
||||
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)
|
||||
|
@ -45,12 +45,6 @@ extension Ghostty {
|
||||
}
|
||||
|
||||
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.
|
||||
self.config = Config()
|
||||
if self.config.config == nil {
|
||||
|
@ -61,9 +61,12 @@ extension Ghostty {
|
||||
/// its up to the env var being set in the correct circumstance.
|
||||
static var launchSource: LaunchSource {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
env.remove("GHOSTTY_MAC_APP");
|
||||
env.remove("GHOSTTY_MAC_LAUNCH_SOURCE");
|
||||
|
||||
// If we were launched from the desktop then we want to
|
||||
// remove the LANGUAGE env var so that we don't inherit
|
||||
|
@ -122,10 +122,6 @@ pub fn init(
|
||||
|
||||
if (b.args) |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;
|
||||
|
@ -46,17 +46,11 @@ const Info = extern struct {
|
||||
};
|
||||
};
|
||||
|
||||
/// Initialize ghostty global state. It is possible to have more than
|
||||
/// one global state but it has zero practical benefit.
|
||||
export fn ghostty_init() c_int {
|
||||
/// Initialize ghostty global state.
|
||||
export fn ghostty_init(argc: usize, argv: [*][*:0]u8) c_int {
|
||||
assert(builtin.link_libc);
|
||||
|
||||
// Since in the lib we don't go through start.zig, we need
|
||||
// to populate argv so that inspecting std.os.argv doesn't
|
||||
// touch uninitialized memory.
|
||||
var argv: [0][*:0]u8 = .{};
|
||||
std.os.argv = &argv;
|
||||
|
||||
std.os.argv = argv[0..argc];
|
||||
state.init() catch |err| {
|
||||
std.log.err("failed to initialize ghostty error={}", .{err});
|
||||
return 1;
|
||||
@ -65,15 +59,17 @@ export fn ghostty_init() c_int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// This is the entrypoint for the CLI version of Ghostty. This
|
||||
/// is mutually exclusive to ghostty_init. Do NOT run ghostty_init
|
||||
/// if you are going to run this. This will not return.
|
||||
export fn ghostty_cli_main(argc: usize, argv: [*][*:0]u8) noreturn {
|
||||
std.os.argv = argv[0..argc];
|
||||
main.main() catch |err| {
|
||||
std.log.err("failed to run ghostty error={}", .{err});
|
||||
/// Runs an action if it is specified. If there is no action this returns
|
||||
/// false. If there is an action then this doesn't return.
|
||||
export fn ghostty_cli_try_action() void {
|
||||
const action = state.action orelse return;
|
||||
std.log.info("executing CLI action={}", .{action});
|
||||
posix.exit(action.run(state.alloc) catch |err| {
|
||||
std.log.err("CLI action failed error={}", .{err});
|
||||
posix.exit(1);
|
||||
};
|
||||
});
|
||||
|
||||
posix.exit(0);
|
||||
}
|
||||
|
||||
/// 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
|
||||
// app bundle (i.e. via open) then we still treat it as if it
|
||||
// was launched from the desktop.
|
||||
if (build_config.artifact == .lib and
|
||||
posix.getenv("GHOSTTY_MAC_APP") != null) break :macos true;
|
||||
if (build_config.artifact == .lib) lib: {
|
||||
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;
|
||||
},
|
||||
|
Reference in New Issue
Block a user