mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-09-14 18:01:58 +00:00
The Zig 0.16.0 update dropped the negation from the std default (!strip_debug_info), disabling stack traces in every unstripped build. AI disclosure: Claude Fable was used to diagnose the problem and find the fix. Commit message was written by me. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QfzQME46DQXwMWa43bQaa3
267 lines
9.4 KiB
Zig
267 lines
9.4 KiB
Zig
//! The main entrypoint for the `ghostty` application. This also serves
|
|
//! as the process initialization code for the `libghostty` library.
|
|
|
|
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const Allocator = std.mem.Allocator;
|
|
const posix = std.posix;
|
|
const build_config = @import("build_config.zig");
|
|
const macos = @import("macos");
|
|
const cli = @import("cli.zig");
|
|
const renderer = @import("renderer.zig");
|
|
const apprt = @import("apprt.zig");
|
|
|
|
const App = @import("App.zig");
|
|
const Ghostty = @import("main_c.zig").Ghostty;
|
|
const global = @import("global.zig");
|
|
|
|
/// The return type for main() depends on the build artifact. The lib build
|
|
/// also calls "main" in order to run the CLI actions, but it calls it as
|
|
/// an API and not an entrypoint.
|
|
const MainReturn = switch (build_config.artifact) {
|
|
.lib => noreturn,
|
|
else => void,
|
|
};
|
|
|
|
pub fn main(minimal: std.process.Init.Minimal) !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;
|
|
// no other Zig code should EVER access the global state.
|
|
global.init(.{ .main = minimal }) catch |err| {
|
|
var buffer: [1024]u8 = undefined;
|
|
var stderr_writer = std.Io.File.stderr().writer(
|
|
std.Io.Threaded.global_single_threaded.io(),
|
|
&buffer,
|
|
);
|
|
const stderr = &stderr_writer.interface;
|
|
defer std.process.exit(1);
|
|
const ErrSet = @TypeOf(err) || error{Unknown};
|
|
switch (@as(ErrSet, @errorCast(err))) {
|
|
error.MultipleActions => try stderr.print(
|
|
"Error: multiple CLI actions specified. You must specify only one\n" ++
|
|
"action starting with the `+` character.\n",
|
|
.{},
|
|
),
|
|
|
|
error.InvalidAction => try stderr.print(
|
|
"Error: unknown CLI action specified. CLI actions are specified with\n" ++
|
|
"the '+' character.\n\n" ++
|
|
"All valid CLI actions can be listed with `ghostty +help`\n",
|
|
.{},
|
|
),
|
|
|
|
else => try stderr.print("invalid CLI invocation err={}\n", .{err}),
|
|
}
|
|
try stderr.flush();
|
|
};
|
|
defer global.deinit();
|
|
const alloc = global.alloc();
|
|
|
|
if (comptime builtin.mode == .Debug) {
|
|
std.log.warn("This is a debug build. Performance will be very poor.", .{});
|
|
std.log.warn("You should only use a debug build for developing Ghostty.", .{});
|
|
std.log.warn("Otherwise, please rebuild in a release mode.", .{});
|
|
}
|
|
|
|
// Execute our action if we have one
|
|
if (global.action()) |action| {
|
|
std.log.info("executing CLI action={}", .{action});
|
|
std.process.exit(action.run(alloc) catch |err| err: {
|
|
std.log.err("CLI action failed error={}", .{err});
|
|
break :err 1;
|
|
});
|
|
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.
|
|
\\
|
|
\\On macOS, the terminal can also be launched using `open -na Ghostty.app`,
|
|
\\or `open -na Ghostty.app --args --foo=bar --baz=qux` to pass arguments.
|
|
\\
|
|
\\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.process.exit(0);
|
|
}
|
|
|
|
// Create our app state
|
|
const app: *App = try App.create(alloc);
|
|
defer app.destroy();
|
|
|
|
// Create our runtime app
|
|
var app_runtime: apprt.App = undefined;
|
|
try app_runtime.init(app, .{});
|
|
defer app_runtime.terminate();
|
|
|
|
// Since - by definition - there are no surfaces when first started, the
|
|
// quit timer may need to be started. The start timer will get cancelled if/
|
|
// when the first surface is created.
|
|
if (@hasDecl(apprt.App, "startQuitTimer")) app_runtime.startQuitTimer();
|
|
|
|
// Run the GUI event loop
|
|
try app_runtime.run();
|
|
}
|
|
|
|
// The function std.log will call.
|
|
fn logFn(
|
|
comptime level: std.log.Level,
|
|
comptime scope: @TypeOf(.EnumLiteral),
|
|
comptime format: []const u8,
|
|
args: anytype,
|
|
) void {
|
|
// On Mac, we use unified logging. To view this:
|
|
//
|
|
// sudo log stream --level debug --predicate 'subsystem=="com.mitchellh.ghostty"'
|
|
//
|
|
// macOS logging is thread safe so no need for locks/mutexes
|
|
macos: {
|
|
if (comptime !builtin.target.os.tag.isDarwin()) break :macos;
|
|
if (!global.logging().macos) break :macos;
|
|
|
|
const prefix = if (scope == .default) "" else @tagName(scope) ++ ": ";
|
|
|
|
// Convert our levels to Mac levels
|
|
const mac_level: macos.os.LogType = switch (level) {
|
|
.debug => .debug,
|
|
.info => .info,
|
|
.warn => .err,
|
|
.err => .fault,
|
|
};
|
|
|
|
macosLogger(scope).log(
|
|
std.heap.c_allocator,
|
|
mac_level,
|
|
prefix ++ format,
|
|
args,
|
|
);
|
|
}
|
|
|
|
stderr: {
|
|
// don't log debug messages to stderr unless we are a debug build
|
|
if (comptime builtin.mode != .Debug and level == .debug) break :stderr;
|
|
|
|
// skip if we are not logging to stderr
|
|
if (!global.logging().stderr) break :stderr;
|
|
|
|
// Lock so we are thread-safe
|
|
var buf: [64]u8 = undefined;
|
|
const stderr = std.debug.lockStderr(&buf);
|
|
defer std.debug.unlockStderr();
|
|
|
|
const level_txt = comptime level.asText();
|
|
const prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
|
|
nosuspend stderr.file_writer.interface.print(level_txt ++ prefix ++ format ++ "\n", args) catch break :stderr;
|
|
nosuspend stderr.file_writer.interface.flush() catch break :stderr;
|
|
}
|
|
}
|
|
|
|
/// Returns the macOS unified logging logger for the given scope. The
|
|
/// logger is created once per scope and cached for the lifetime of the
|
|
/// process, because os_log object creation is slow (it shows up in
|
|
/// startup profiles when done per log call) and Apple's guidance is to
|
|
/// create loggers once and reuse them.
|
|
fn macosLogger(comptime scope: @TypeOf(.EnumLiteral)) *macos.os.Log {
|
|
const S = struct {
|
|
var cached: std.atomic.Value(?*macos.os.Log) = .init(null);
|
|
};
|
|
|
|
if (S.cached.load(.acquire)) |v| return v;
|
|
|
|
// Create and attempt to store our logger. If we race with another
|
|
// thread then we use theirs and release ours.
|
|
const created = macos.os.Log.create(
|
|
build_config.bundle_id,
|
|
@tagName(scope),
|
|
);
|
|
if (S.cached.cmpxchgStrong(
|
|
null,
|
|
created,
|
|
.acq_rel,
|
|
.acquire,
|
|
)) |existing| {
|
|
created.release();
|
|
return existing.?;
|
|
}
|
|
|
|
return created;
|
|
}
|
|
|
|
pub const std_options: std.Options = .{
|
|
// Our log level is always at least info in every build mode.
|
|
//
|
|
// Note, we don't lower this to debug even with conditional logging
|
|
// via GHOSTTY_LOG because our debug logs are very expensive to
|
|
// calculate and we want to make sure they're optimized out in
|
|
// builds.
|
|
.log_level = switch (builtin.mode) {
|
|
.Debug => .debug,
|
|
else => .info,
|
|
},
|
|
|
|
.logFn = logFn,
|
|
|
|
// If are building for a non-MacOS Darwin target (e.g., iOS), we need to
|
|
// disable stack tracing for the time being. This is due to the fact that
|
|
// Zig switched to using _dyld_get_image_header_containing_address and some
|
|
// other (deprecated) calls to speed up stack unwinding; these calls are
|
|
// available on MacOS, but not on other platforms.
|
|
//
|
|
// A fix has already been submitted to exempt non-MacOS (but still Darwin)
|
|
// targets, so this can likely be removed in Zig 0.17.0, or a 0.16.x patch
|
|
// version if it releases beforehand.
|
|
//
|
|
// More details:
|
|
// https://codeberg.org/ziglang/zig/commit/89f86e46d278a35a613bbc662cdd3f65ffc76ed7
|
|
//
|
|
.allow_stack_tracing = if (builtin.target.os.tag.isDarwin() and builtin.target.os.tag != .macos)
|
|
false
|
|
else
|
|
!builtin.strip_debug_info,
|
|
};
|
|
|
|
test {
|
|
_ = @import("pty.zig");
|
|
_ = @import("Command.zig");
|
|
_ = @import("font/main.zig");
|
|
_ = @import("apprt.zig");
|
|
_ = @import("renderer.zig");
|
|
_ = @import("termio.zig");
|
|
_ = @import("input.zig");
|
|
_ = @import("cli.zig");
|
|
_ = @import("surface_mouse.zig");
|
|
|
|
// Libraries
|
|
_ = @import("tripwire.zig");
|
|
_ = @import("benchmark/main.zig");
|
|
_ = @import("crash/main.zig");
|
|
_ = @import("datastruct/main.zig");
|
|
_ = @import("inspector/main.zig");
|
|
_ = @import("lib/main.zig");
|
|
_ = @import("terminal/main.zig");
|
|
_ = @import("terminfo/main.zig");
|
|
_ = @import("simd/main.zig");
|
|
_ = @import("synthetic/main.zig");
|
|
_ = @import("unicode/main.zig");
|
|
_ = @import("unicode/props_uucode.zig");
|
|
_ = @import("unicode/symbols_uucode.zig");
|
|
|
|
// Extra
|
|
_ = @import("extra/bash.zig");
|
|
_ = @import("extra/fish.zig");
|
|
_ = @import("extra/sublime.zig");
|
|
_ = @import("extra/vim.zig");
|
|
_ = @import("extra/zsh.zig");
|
|
}
|