mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 09:16:11 +03:00
Merge pull request #500 from mitchellh/macos-cli
macos: Ghostty binary is also a CLI app
This commit is contained in:
@ -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();
|
||||
|
@ -2,7 +2,10 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>LSEnvironment</key>
|
||||
<dict>
|
||||
<key>GHOSTTY_MAC_APP</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -32,6 +32,7 @@
|
||||
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDB29B8009000646FDA /* SplitView.swift */; };
|
||||
A5CEAFDE29B8058B00646FDA /* SplitView.Divider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */; };
|
||||
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; };
|
||||
A5FEB3002ABB69450068369E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FEB2FF2ABB69450068369E /* main.swift */; };
|
||||
A5FECBD729D1FC3900022361 /* PrimaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FECBD629D1FC3900022361 /* PrimaryView.swift */; };
|
||||
A5FECBD929D2010400022361 /* WindowAccessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FECBD829D2010400022361 /* WindowAccessor.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
@ -65,6 +66,7 @@
|
||||
A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.Divider.swift; sourceTree = "<group>"; };
|
||||
A5CEAFFE29C2410700646FDA /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = "<group>"; };
|
||||
A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = "<group>"; };
|
||||
A5FEB2FF2ABB69450068369E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||
A5FECBD629D1FC3900022361 /* PrimaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryView.swift; sourceTree = "<group>"; };
|
||||
A5FECBD829D2010400022361 /* WindowAccessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowAccessor.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@ -128,6 +130,7 @@
|
||||
A54CD6ED299BEB14008C95BB /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A5FEB2FF2ABB69450068369E /* main.swift */,
|
||||
A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */,
|
||||
857F63802A5E64F200CA4815 /* MainMenu.xib */,
|
||||
A53426362A7DC53000EBB7A2 /* Features */,
|
||||
@ -279,6 +282,7 @@
|
||||
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
|
||||
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */,
|
||||
A5FECBD729D1FC3900022361 /* PrimaryView.swift in Sources */,
|
||||
A5FEB3002ABB69450068369E /* main.swift in Sources */,
|
||||
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */,
|
||||
A55B7BBE29B701360055DE60 /* Ghostty.SplitView.swift in Sources */,
|
||||
A55B7BB629B6F47F0055DE60 /* AppState.swift in Sources */,
|
||||
@ -424,11 +428,13 @@
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
EXECUTABLE_NAME = ghostty;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Ghostty-Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
@ -457,12 +463,14 @@
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
EXECUTABLE_NAME = ghostty;
|
||||
GCC_OPTIMIZATION_LEVEL = fast;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Ghostty-Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
|
@ -2,7 +2,6 @@ import AppKit
|
||||
import OSLog
|
||||
import GhosttyKit
|
||||
|
||||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyAppStateDelegate {
|
||||
// The application logger. We should probably move this at some point to a dedicated
|
||||
// class/struct but for now it lives here! 🤷♂️
|
||||
|
14
macos/Sources/main.swift
Normal file
14
macos/Sources/main.swift
Normal file
@ -0,0 +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"] ?? "") == "") {
|
||||
ghostty_cli_main(UInt(CommandLine.argc), CommandLine.unsafeArgv)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
|
43
src/main.zig
43
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 +<action> [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,14 +226,16 @@ 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 = {} };
|
||||
|
||||
// For lib mode we always disable stderr logging by default.
|
||||
if (comptime build_config.app_runtime == .none) {
|
||||
self.logging = .{ .disabled = {} };
|
||||
}
|
||||
|
||||
// I don't love the env var name but I don't have it in my heart
|
||||
|
@ -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) {
|
||||
|
48
src/os/env.zig
Normal file
48
src/os/env.zig
Normal file
@ -0,0 +1,48 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
/// Append a value to an environment variable such as PATH.
|
||||
/// The returned value is always allocated so it must be freed.
|
||||
pub fn appendEnv(
|
||||
alloc: Allocator,
|
||||
current: []const u8,
|
||||
value: []const u8,
|
||||
) ![]u8 {
|
||||
// If there is no prior value, we return it as-is
|
||||
if (current.len == 0) return try alloc.dupe(u8, value);
|
||||
|
||||
// Otherwise we must prefix.
|
||||
const sep = switch (builtin.os.tag) {
|
||||
.windows => ";",
|
||||
else => ":",
|
||||
};
|
||||
|
||||
return try std.fmt.allocPrint(alloc, "{s}{s}{s}", .{
|
||||
current,
|
||||
sep,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
test "appendEnv empty" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
const result = try appendEnv(alloc, "", "foo");
|
||||
defer alloc.free(result);
|
||||
try testing.expectEqualStrings(result, "foo");
|
||||
}
|
||||
|
||||
test "appendEnv existing" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
const result = try appendEnv(alloc, "a:b", "foo");
|
||||
defer alloc.free(result);
|
||||
if (builtin.os.tag == .windows) {
|
||||
try testing.expectEqualStrings(result, "a:b;foo");
|
||||
} else {
|
||||
try testing.expectEqualStrings(result, "a:b:foo");
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
//! The "os" package contains utilities for interfacing with the operating
|
||||
//! system.
|
||||
|
||||
pub usingnamespace @import("env.zig");
|
||||
pub usingnamespace @import("file.zig");
|
||||
pub usingnamespace @import("flatpak.zig");
|
||||
pub usingnamespace @import("homedir.zig");
|
||||
|
Reference in New Issue
Block a user