diff --git a/src/os/main.zig b/src/os/main.zig index 3c2afcb31..b80c116e6 100644 --- a/src/os/main.zig +++ b/src/os/main.zig @@ -7,6 +7,7 @@ pub usingnamespace @import("homedir.zig"); pub usingnamespace @import("locale.zig"); pub usingnamespace @import("macos_version.zig"); pub usingnamespace @import("mouse.zig"); +pub usingnamespace @import("resourcesdir.zig"); pub const TempDir = @import("TempDir.zig"); pub const passwd = @import("passwd.zig"); pub const xdg = @import("xdg.zig"); diff --git a/src/os/resourcesdir.zig b/src/os/resourcesdir.zig new file mode 100644 index 000000000..f7e576c5f --- /dev/null +++ b/src/os/resourcesdir.zig @@ -0,0 +1,74 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Allocator = std.mem.Allocator; + +/// Gets the directory to the bundled resources directory, if it +/// exists (not all platforms or packages have it). The output is +/// written to the given buffer if we need to allocate. Note that +/// the output is not ALWAYS written to the buffer and may refer to +/// static memory. +/// +/// This returns error.OutOfMemory is buffer is not big enough. +pub fn resourcesDir(buf: []u8) !?[]const u8 { + // If we have an environment variable set, we always use that. + if (std.os.getenv("GHOSTTY_RESOURCES_DIR")) |dir| { + if (dir.len > 0) { + return dir; + } + } + + // This is the sentinel value we look for in the path to know + // we've found the resources directory. + const sentinel = "terminfo/ghostty.termcap"; + + // 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 null; + + // We have an exe path! Climb the tree looking for the terminfo + // bundle as we expect it. + while (std.fs.path.dirname(exe)) |dir| { + exe = dir; + + // On MacOS, we look for the app bundle path. + if (comptime builtin.target.isDarwin()) { + if (try maybeDir(buf, dir, "Contents/Resources", sentinel)) |v| { + return v; + } + } + + // On all platforms, 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. + if (try maybeDir(buf, dir, "share", sentinel)) |v| { + return v; + } + } + + return null; +} + +/// 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; +}