mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-12-30 01:54:49 +00:00
140 lines
5.2 KiB
Zig
140 lines
5.2 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
pub const ResourcesDir = struct {
|
|
/// Avoid accessing these directly, use the app() and host() methods instead.
|
|
app_path: ?[]const u8 = null,
|
|
host_path: ?[]const u8 = null,
|
|
|
|
/// Free resources held. Requires the same allocator as when resourcesDir()
|
|
/// is called.
|
|
pub fn deinit(self: *ResourcesDir, alloc: Allocator) void {
|
|
if (self.app_path) |p| alloc.free(p);
|
|
if (self.host_path) |p| alloc.free(p);
|
|
}
|
|
|
|
/// Get the directory to the bundled resources directory accessible
|
|
/// by the application.
|
|
pub fn app(self: *ResourcesDir) ?[]const u8 {
|
|
return self.app_path;
|
|
}
|
|
|
|
/// Get the directory to the bundled resources directory accessible
|
|
/// by the host environment (i.e. for sandboxed applications). The
|
|
/// returned directory might not be accessible from the application
|
|
/// itself.
|
|
///
|
|
/// In non-sandboxed environment, this should be the same as app().
|
|
pub fn host(self: *ResourcesDir) ?[]const u8 {
|
|
return self.host_path orelse self.app_path;
|
|
}
|
|
};
|
|
|
|
/// Gets the directory to the bundled resources directory, if it
|
|
/// exists (not all platforms or packages have it). The output is
|
|
/// owned by the caller.
|
|
///
|
|
/// This is highly Ghostty-specific and can likely be generalized at
|
|
/// some point but we can cross that bridge if we ever need to.
|
|
pub fn resourcesDir(alloc: Allocator) !ResourcesDir {
|
|
// Use the GHOSTTY_RESOURCES_DIR environment variable in release builds.
|
|
//
|
|
// In debug builds we try using terminfo detection first instead, since
|
|
// if debug Ghostty is launched by an older version of Ghostty, it
|
|
// would inherit the old, stale resources of older Ghostty instead of the
|
|
// freshly built ones under zig-out/share/ghostty.
|
|
//
|
|
// Note: we ALWAYS want to allocate here because the result is always
|
|
// freed, do not try to use internal_os.getenv or posix getenv.
|
|
if (comptime builtin.mode != .Debug) {
|
|
if (std.process.getEnvVarOwned(alloc, "GHOSTTY_RESOURCES_DIR")) |dir| {
|
|
if (dir.len > 0) return .{ .app_path = dir };
|
|
} else |err| switch (err) {
|
|
error.EnvironmentVariableNotFound => {},
|
|
else => return err,
|
|
}
|
|
}
|
|
|
|
// This is the sentinel value we look for in the path to know
|
|
// we've found the resources directory.
|
|
const sentinels = switch (comptime builtin.target.os.tag) {
|
|
.windows => .{"terminfo/ghostty.terminfo"},
|
|
.macos => .{"terminfo/78/xterm-ghostty"},
|
|
.freebsd => .{ "site-terminfo/g/ghostty", "site-terminfo/x/xterm-ghostty" },
|
|
else => .{ "terminfo/g/ghostty", "terminfo/x/xterm-ghostty" },
|
|
};
|
|
|
|
// Get the path to our running binary
|
|
var exe_buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
var exe: []const u8 = std.fs.selfExePath(&exe_buf) catch return .{};
|
|
|
|
// We have an exe path! Climb the tree looking for the terminfo
|
|
// bundle as we expect it.
|
|
var dir_buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
while (std.fs.path.dirname(exe)) |dir| {
|
|
exe = dir;
|
|
|
|
// On MacOS, we look for the app bundle path.
|
|
if (comptime builtin.target.os.tag.isDarwin()) {
|
|
inline for (sentinels) |sentinel| {
|
|
if (try maybeDir(&dir_buf, dir, "Contents/Resources", sentinel)) |v| {
|
|
return .{ .app_path = try std.fs.path.join(alloc, &.{ v, "ghostty" }) };
|
|
}
|
|
}
|
|
}
|
|
|
|
// On all platforms (except BSD), we look for a /usr/share style path. This
|
|
// is valid even on Mac since there is nothing that requires
|
|
// Ghostty to be in an app bundle.
|
|
inline for (sentinels) |sentinel| {
|
|
if (try maybeDir(
|
|
&dir_buf,
|
|
dir,
|
|
if (builtin.target.os.tag == .freebsd) "local/share" else "share",
|
|
sentinel,
|
|
)) |v| {
|
|
return .{ .app_path = try std.fs.path.join(alloc, &.{ v, "ghostty" }) };
|
|
}
|
|
}
|
|
}
|
|
|
|
// If terminfo detection failed in debug builds (somehow),
|
|
// fallback and use the provided resources dir.
|
|
if (comptime builtin.mode == .Debug) {
|
|
if (std.process.getEnvVarOwned(alloc, "GHOSTTY_RESOURCES_DIR")) |dir| {
|
|
if (dir.len > 0) return .{ .app_path = dir };
|
|
} else |err| switch (err) {
|
|
error.EnvironmentVariableNotFound => {},
|
|
else => return err,
|
|
}
|
|
}
|
|
|
|
return .{};
|
|
}
|
|
|
|
/// Little helper to check if the "base/sub/suffix" directory exists and
|
|
/// if so return true. The "suffix" is just used as a way to verify a directory
|
|
/// seems roughly right.
|
|
///
|
|
/// "buf" must be large enough to fit base + sub + suffix. This is generally
|
|
/// max_path_bytes so its not a big deal.
|
|
pub fn maybeDir(
|
|
buf: []u8,
|
|
base: []const u8,
|
|
sub: []const u8,
|
|
suffix: []const u8,
|
|
) !?[]const u8 {
|
|
const path = try std.fmt.bufPrint(buf, "{s}/{s}/{s}", .{ base, sub, suffix });
|
|
|
|
if (std.fs.accessAbsolute(path, .{})) {
|
|
const len = path.len - suffix.len - 1;
|
|
return buf[0..len];
|
|
} else |_| {
|
|
// Folder doesn't exist. If a different error happens its okay
|
|
// we just ignore it and move on.
|
|
}
|
|
|
|
return null;
|
|
}
|