Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236const std = @import("std");const builtin = @import("builtin");const build_config = @import("build_config.zig");const cli = @import("cli.zig");const internal_os = @import("os/main.zig");const fontconfig = @import("fontconfig");const glslang = @import("glslang");const harfbuzz = @import("harfbuzz");const oni = @import("oniguruma");const crash = @import("crash/main.zig");const renderer = @import("renderer.zig");const apprt = @import("apprt.zig");
/// We export the xev backend we want to use so that the rest of/// Ghostty can import this once and have access to the proper/// backend.pub const xev = @import("xev").Dynamic;
/// Global process state. This is initialized in main() for exe artifacts/// and by ghostty_init() for lib artifacts. This should ONLY be used by/// the C API. The Zig API should NOT use any global state and should/// rely on allocators being passed in as parameters.pub var state: GlobalState = undefined;
/// This represents the global process state. There should only/// be one of these at any given moment. This is extracted into a dedicated/// struct because it is reused by main and the static C lib.pub const GlobalState = struct { const GPA = std.heap.GeneralPurposeAllocator(.{});
gpa: ?GPA, alloc: std.mem.Allocator, action: ?cli.ghostty.Action, logging: Logging, rlimits: ResourceLimits = .{},
/// The app resources directory, equivalent to zig-out/share when we build /// from source. This is null if we can't detect it. resources_dir: internal_os.ResourcesDir,
/// Where logging should go pub const Logging = packed struct { /// Whether to log to stderr. For lib mode we always disable stderr /// logging by default. Otherwise it's enabled by default. stderr: bool = build_config.app_runtime != .none, /// Whether to log to macOS's unified logging. Enabled by default /// on macOS. macos: bool = builtin.os.tag.isDarwin(), };
/// Initialize the global state. pub fn init(self: *GlobalState) !void { // const start = try std.time.Instant.now(); // const start_micro = std.time.microTimestamp(); // defer { // const end = std.time.Instant.now() catch unreachable; // // "[updateFrame critical time] <START us>\t<TIME_TAKEN us>" // std.log.err("[global init time] start={}us duration={}ns", .{ start_micro, end.since(start) / std.time.ns_per_us }); // }
// Initialize ourself to nothing so we don't have any extra state. // IMPORTANT: this MUST be initialized before any log output because // the log function uses the global state. self.* = .{ .gpa = null, .alloc = undefined, .action = null, .logging = .{}, .rlimits = .{}, .resources_dir = .{}, }; errdefer self.deinit();
self.gpa = gpa: { // Use the libc allocator if it is available because it is WAY // faster than GPA. We only do this in release modes so that we // can get easy memory leak detection in debug modes. if (builtin.link_libc) { if (switch (builtin.mode) { .ReleaseSafe, .ReleaseFast => true,
// We also use it if we can detect we're running under // Valgrind since Valgrind only instruments the C allocator else => std.valgrind.runningOnValgrind() > 0, }) break :gpa null; }
break :gpa GPA{}; };
self.alloc = if (self.gpa) |*value| value.allocator() else if (builtin.link_libc) std.heap.c_allocator else unreachable;
// We first try to parse any action that we may be executing. self.action = try cli.action.detectArgs( cli.ghostty.Action, 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.stderr = false;
// 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, // maybe once for logging) so for now this is an easy way to do // this. Env vars are useful for logging too because they are // easy to set. if ((try internal_os.getenv(self.alloc, "GHOSTTY_LOG"))) |v| { defer v.deinit(self.alloc); self.logging = cli.args.parsePackedStruct(Logging, v.value) catch .{}; }
// Setup our signal handlers before logging initSignals();
// Setup our Xev backend if we're dynamic if (comptime xev.dynamic) xev.detect() catch |err| { std.log.warn("failed to detect xev backend, falling back to " ++ "most compatible backend err={}", .{err}); };
// Output some debug information right away std.log.info("ghostty version={s}", .{build_config.version_string}); std.log.info("ghostty build optimize={s}", .{build_config.mode_string}); std.log.info("runtime={}", .{build_config.app_runtime}); std.log.info("font_backend={}", .{build_config.font_backend}); if (comptime build_config.font_backend.hasHarfbuzz()) { std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()}); } if (comptime build_config.font_backend.hasFontconfig()) { std.log.info("dependency fontconfig={d}", .{fontconfig.version()}); } std.log.info("renderer={}", .{renderer.Renderer}); std.log.info("libxev default backend={t}", .{xev.backend});
// As early as possible, initialize our resource limits. self.rlimits = .init();
// Initialize our crash reporting. crash.init(self.alloc) catch |err| { std.log.warn( "sentry init failed, no crash capture available err={}", .{err}, ); };
// const sentrylib = @import("sentry"); // if (sentrylib.captureEvent(sentrylib.Value.initMessageEvent( // .info, // null, // "hello, world", // ))) |uuid| { // std.log.warn("uuid={s}", .{uuid.string()}); // } else std.log.warn("failed to capture event", .{});
// We need to make sure the process locale is set properly. Locale // affects a lot of behaviors in a shell. try internal_os.ensureLocale(self.alloc);
// Initialize glslang for shader compilation try glslang.init();
// Initialize oniguruma for regex try oni.init(&.{oni.Encoding.utf8});
// Find our resources directory once for the app so every launch // hereafter can use this cached value. self.resources_dir = try apprt.runtime.resourcesDir(self.alloc); errdefer self.resources_dir.deinit(self.alloc);
// Setup i18n if (self.resources_dir.app()) |v| internal_os.i18n.init(v) catch |err| { std.log.warn("failed to init i18n, translations will not be available err={}", .{err}); }; }
/// Cleans up the global state. This doesn't _need_ to be called but /// doing so in dev modes will check for memory leaks. pub fn deinit(self: *GlobalState) void { self.resources_dir.deinit(self.alloc);
// Flush our crash logs crash.deinit();
if (self.gpa) |*value| { // We want to ensure that we deinit the GPA because this is // the point at which it will output if there were safety violations. _ = value.deinit(); } }
fn initSignals() void { // Only posix systems. if (comptime builtin.os.tag == .windows) return;
const p = std.posix;
var sa: p.Sigaction = .{ .handler = .{ .handler = p.SIG.IGN }, .mask = p.sigemptyset(), .flags = 0, };
// We ignore SIGPIPE because it is a common signal we may get // due to how we implement termio. When a terminal is closed we // often write to a broken pipe to exit the read thread. This should // be fixed one day but for now this helps make this a bit more // robust. p.sigaction(p.SIG.PIPE, &sa, null); }};
/// Maintains the Unix resource limits that we set for our process. This/// can be used to restore the limits to their original values.pub const ResourceLimits = struct { nofile: ?internal_os.rlimit = null,
pub fn init() ResourceLimits { return .{ // Maximize the number of file descriptors we can have open // because we can consume a lot of them if we make many terminals. .nofile = internal_os.fixMaxFiles(), }; }
pub fn restore(self: *const ResourceLimits) void { if (self.nofile) |lim| internal_os.restoreMaxFiles(lim); }};