diff --git a/src/Surface.zig b/src/Surface.zig index 2d24647c0..382b35e1b 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -182,6 +182,7 @@ pub fn init( alloc: Allocator, config: *const configpkg.Config, app_mailbox: App.Mailbox, + app_resources_dir: ?[]const u8, rt_surface: *apprt.runtime.Surface, ) !void { // Initialize our renderer with our initialized surface. @@ -405,6 +406,7 @@ pub fn init( .screen_size = screen_size, .full_config = config, .config = try termio.Impl.DerivedConfig.init(alloc, config), + .resources_dir = app_resources_dir, .renderer_state = &self.renderer_state, .renderer_wakeup = render_thread.wakeup, .renderer_mailbox = render_thread.mailbox, diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 4db75f31f..0cef7251f 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -363,6 +363,7 @@ pub const Surface = struct { app.app.alloc, &config, .{ .rt_app = app, .mailbox = &app.app.mailbox }, + app.app.resources_dir, self, ); errdefer self.core_surface.deinit(); diff --git a/src/apprt/gtk.zig b/src/apprt/gtk.zig index 349efde5d..8c671be4f 100644 --- a/src/apprt/gtk.zig +++ b/src/apprt/gtk.zig @@ -765,6 +765,7 @@ pub const Surface = struct { self.app.core_app.alloc, &config, .{ .rt_app = self.app, .mailbox = &self.app.core_app.mailbox }, + self.app.core_app.resources_dir, self, ); errdefer self.core_surface.deinit(); diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 5f6c70aac..3ed481ef2 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -544,26 +544,12 @@ const Subprocess = struct { }; errdefer env.deinit(); - // Get our bundled resources directory, if it exists. We use this - // for terminfo, shell-integration, etc. + // If we have a resources dir then set our env var const resources_key = "GHOSTTY_RESOURCES_DIR"; - const resources_dir = dir: { - if (env.get(resources_key)) |dir| { - if (dir.len > 0) { - log.info("using Ghostty resources dir from env var: {s}", .{dir}); - break :dir dir; - } - } - - if (try resourcesDir(alloc)) |dir| { - log.info("found Ghostty resources dir: {s}", .{dir}); - try env.put(resources_key, dir); - break :dir dir; - } - - log.warn("Ghostty resources dir not found, some features disabled", .{}); - break :dir null; - }; + if (opts.resources_dir) |dir| { + log.info("found Ghostty resources dir: {s}", .{dir}); + try env.put(resources_key, dir); + } // Set our TERM var. This is a bit complicated because we want to use // the ghostty TERM value but we want to only do that if we have @@ -571,7 +557,9 @@ const Subprocess = struct { // // For now, we just look up a bundled dir but in the future we should // also load the terminfo database and look for it. - if (try terminfoDir(alloc, resources_dir)) |dir| { + if (opts.resources_dir) |base| { + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const dir = try std.fmt.bufPrint(&buf, "{s}/terminfo", .{base}); try env.put("TERM", "xterm-ghostty"); try env.put("COLORTERM", "truecolor"); try env.put("TERMINFO", dir); @@ -641,7 +629,7 @@ const Subprocess = struct { .zsh => .zsh, }; - const dir = resources_dir orelse break :shell null; + const dir = opts.resources_dir orelse break :shell null; break :shell try shell_integration.setup( dir, final_path, @@ -860,73 +848,6 @@ const Subprocess = struct { fn killCommandFlatpak(command: *FlatpakHostCommand) !void { try command.signal(c.SIGHUP, true); } - - /// Gets the directory to the terminfo database, if it can be detected. - /// The memory returned can't be easily freed so the alloc should be - /// an arena or something similar. - fn terminfoDir(alloc: Allocator, base: ?[]const u8) !?[]const u8 { - const dir = base orelse return null; - return try tryDir(alloc, dir, "terminfo", ""); - } - - /// Gets the directory to the bundled resources directory, if it - /// exists (not all platforms or packages have it). - /// - /// The memory returned can't be easily freed so the alloc should be - /// an arena or something similar. - fn resourcesDir(alloc: Allocator) !?[]const u8 { - // 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 tryDir(alloc, 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 tryDir(alloc, dir, "share", sentinel)) |v| { - return v; - } - } - - return null; - } - - /// Little helper to check if the "base/sub/suffix" directory exists and - /// if so, duplicate the "base/sub" path and return it. - fn tryDir( - alloc: Allocator, - base: []const u8, - sub: []const u8, - suffix: []const u8, - ) !?[]const u8 { - var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - 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 try alloc.dupe(u8, path[0..len]); - } else |_| { - // Folder doesn't exist. If a different error happens its okay - // we just ignore it and move on. - } - - return null; - } }; /// The read thread sits in a loop doing the following pseudo code: diff --git a/src/termio/Options.zig b/src/termio/Options.zig index 5e88b1010..cad5d5665 100644 --- a/src/termio/Options.zig +++ b/src/termio/Options.zig @@ -20,6 +20,9 @@ full_config: *const Config, /// The derived configuration for this termio implementation. config: termio.Impl.DerivedConfig, +/// The application resources directory. +resources_dir: ?[]const u8, + /// The render state. The IO implementation can modify anything here. The /// surface thread will setup the initial "terminal" pointer but the IO impl /// is free to change that if that is useful (i.e. doing some sort of dual