diff --git a/include/ghostty.h b/include/ghostty.h index 1bdb4fc31..42e83db5f 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -351,6 +351,7 @@ typedef struct { // Published API int ghostty_init(void); +void ghostty_cli_main(uintptr_t, char **); ghostty_info_s ghostty_info(void); ghostty_config_t ghostty_config_new(); diff --git a/macos/Sources/main.swift b/macos/Sources/main.swift index ee38c8d55..9a462b440 100644 --- a/macos/Sources/main.swift +++ b/macos/Sources/main.swift @@ -1,12 +1,14 @@ 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"] == "") { - AppDelegate.logger.warning("NOT IN THE MAC APP") + ghostty_cli_main(UInt(CommandLine.argc), CommandLine.unsafeArgv) + exit(1) } _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) diff --git a/src/main.zig b/src/main.zig index c61f94c5e..37e35e490 100644 --- a/src/main.zig +++ b/src/main.zig @@ -23,7 +23,13 @@ const Ghostty = @import("main_c.zig").Ghostty; /// rely on allocators being passed in as parameters. pub var state: GlobalState = undefined; -pub fn main() !void { +/// The return type for main() depends on the build artifact. +const MainReturn = switch (build_config.artifact) { + .lib => noreturn, + else => void, +}; + +pub fn main() !MainReturn { // We first start by initializing our global state. This will setup // process-level state we need to run the terminal. The reason we use // a global is because the C API needs to be able to access this state; @@ -67,6 +73,24 @@ pub fn main() !void { return; } + if (comptime build_config.app_runtime == .none) { + const stdout = std.io.getStdOut().writer(); + try stdout.print("Usage: ghostty + [flags]\n\n", .{}); + try stdout.print( + \\This is the Ghostty helper CLI that accompanies the graphical Ghostty app. + \\To launch the terminal directly, please launch the graphical app + \\(i.e. Ghostty.app on macOS). This CLI can be used to perform various + \\actions such as inspecting the version, listing fonts, etc. + \\ + \\We don't have proper help output yet, sorry! Please refer to the + \\source code or Discord community for help for now. We'll fix this in time. + , + .{}, + ); + + std.os.exit(0); + } + // Create our app state var app = try App.create(alloc); defer app.destroy(); @@ -156,6 +180,7 @@ pub const GlobalState = struct { stderr: void, }; + /// Initialize the global state. pub fn init(self: *GlobalState) !void { // Initialize ourself to nothing so we don't have any extra state. // IMPORTANT: this MUST be initialized before any log output because @@ -201,15 +226,12 @@ pub const GlobalState = struct { }; // We first try to parse any action that we may be executing. - // We do not execute this in the lib because os.argv is not set. - if (comptime build_config.artifact != .lib) { - self.action = try cli_action.Action.detectCLI(self.alloc); + self.action = try cli_action.Action.detectCLI(self.alloc); - // If we have an action executing, we disable logging by default - // since we write to stderr we don't want logs messing up our - // output. - if (self.action != null) self.logging = .{ .disabled = {} }; - } + // If we have an action executing, we disable logging by default + // since we write to stderr we don't want logs messing up our + // output. + if (self.action != null) self.logging = .{ .disabled = {} }; // I don't love the env var name but I don't have it in my heart // to parse CLI args 3 times (once for actions, once for config, diff --git a/src/main_c.zig b/src/main_c.zig index ec3441582..992c20d19 100644 --- a/src/main_c.zig +++ b/src/main_c.zig @@ -42,13 +42,33 @@ const Info = extern struct { /// one global state but it has zero practical benefit. export fn ghostty_init() 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; + main.state.init() catch |err| { std.log.err("failed to initialize ghostty error={}", .{err}); return 1; }; + 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}); + std.os.exit(1); + }; +} + +/// Return metadata about Ghostty, such as version, build mode, etc. export fn ghostty_info() Info { return .{ .mode = switch (builtin.mode) {