diff --git a/src/os/macos.zig b/src/os/macos.zig index 53dfd17199..d9ce1a5ae9 100644 --- a/src/os/macos.zig +++ b/src/os/macos.zig @@ -25,43 +25,23 @@ pub fn appSupportDir( alloc: Allocator, sub_path: []const u8, ) AppSupportDirError![]u8 { - comptime assert(builtin.target.isDarwin()); - - const NSFileManager = objc.getClass("NSFileManager").?; - const manager = NSFileManager.msgSend( - objc.Object, - objc.sel("defaultManager"), - .{}, - ); - - const url = manager.msgSend( - objc.Object, - objc.sel("URLForDirectory:inDomain:appropriateForURL:create:error:"), - .{ - NSSearchPathDirectory.NSApplicationSupportDirectory, - NSSearchPathDomainMask.NSUserDomainMask, - @as(?*anyopaque, null), - true, - @as(?*anyopaque, null), - }, - ); - - // I don't think this is possible but just in case. - if (url.value == null) return error.AppleAPIFailed; - - // Get the UTF-8 string from the URL. - const path = url.getProperty(objc.Object, "path"); - const c_str = path.getProperty(?[*:0]const u8, "UTF8String") orelse - return error.AppleAPIFailed; - const app_support_dir = std.mem.sliceTo(c_str, 0); - - return try std.fs.path.join(alloc, &.{ - app_support_dir, + return try getSystemPath(alloc, .NSApplicationSupportDirectory, &.{ build_config.bundle_id, sub_path, }); } +pub const CacheDirError = Allocator.Error || error{AppleAPIFailed}; + +/// Return the path to the system cache directory with the given sub path joined. +/// This allocates the result using the given allocator. +pub fn cacheDir( + alloc: Allocator, + sub_path: []const u8, +) CacheDirError![]u8 { + return try getSystemPath(alloc, .NSCachesDirectory, &.{sub_path}); +} + pub const SetQosClassError = error{ // The thread can't have its QoS class changed usually because // a different pthread API was called that makes it an invalid @@ -110,9 +90,52 @@ pub const NSOperatingSystemVersion = extern struct { }; pub const NSSearchPathDirectory = enum(c_ulong) { + NSCachesDirectory = 13, NSApplicationSupportDirectory = 14, }; pub const NSSearchPathDomainMask = enum(c_ulong) { NSUserDomainMask = 1, }; + +fn getSystemPath( + alloc: Allocator, + directory: NSSearchPathDirectory, + sub_paths: []const []const u8, +) (error{AppleAPIFailed} || Allocator.Error)![]u8 { + comptime assert(builtin.target.isDarwin()); + + const NSFileManager = objc.getClass("NSFileManager").?; + const manager = NSFileManager.msgSend( + objc.Object, + objc.sel("defaultManager"), + .{}, + ); + + const url = manager.msgSend( + objc.Object, + objc.sel("URLForDirectory:inDomain:appropriateForURL:create:error:"), + .{ + directory, + NSSearchPathDomainMask.NSUserDomainMask, + @as(?*anyopaque, null), + true, + @as(?*anyopaque, null), + }, + ); + + if (url.value == null) return error.AppleAPIFailed; + + const path = url.getProperty(objc.Object, "path"); + const c_str = path.getProperty(?[*:0]const u8, "UTF8String") orelse + return error.AppleAPIFailed; + const base_dir = std.mem.sliceTo(c_str, 0); + + // Create a new array with base_dir as the first element + var paths = try alloc.alloc([]const u8, sub_paths.len + 1); + paths[0] = base_dir; + @memcpy(paths[1..], sub_paths); + defer alloc.free(paths); + + return try std.fs.path.join(alloc, paths); +} diff --git a/src/os/xdg.zig b/src/os/xdg.zig index 1d60374efc..a57da2159a 100644 --- a/src/os/xdg.zig +++ b/src/os/xdg.zig @@ -7,6 +7,8 @@ const assert = std.debug.assert; const Allocator = std.mem.Allocator; const posix = std.posix; const homedir = @import("homedir.zig"); +const objc = @import("objc"); +const macos = @import("macos.zig"); pub const Options = struct { /// Subdirectories to join to the base. This avoids extra allocations @@ -30,22 +32,17 @@ pub fn config(alloc: Allocator, opts: Options) ![]u8 { /// Get the XDG cache directory. The returned value is allocated. pub fn cache(alloc: Allocator, opts: Options) ![]u8 { - // On macOS we should use ~/Library/Caches instead of ~/.cache - if (builtin.os.tag == .macos) { - // Get our home dir if not provided - const home = if (opts.home) |h| h else blk: { - var buf: [1024]u8 = undefined; - break :blk try homedir.home(&buf) orelse return error.NoHomeDir; - }; - + if (posix.getenv("XDG_CACHE_HOME")) |env| { return try std.fs.path.join(alloc, &[_][]const u8{ - home, - "Library", - "Caches", + env, opts.subdir orelse "", }); } + if (builtin.os.tag == .macos) { + return try macos.cacheDir(alloc, opts.subdir orelse ""); + } + return try dir(alloc, opts, .{ .env = "XDG_CACHE_HOME", .windows_env = "LOCALAPPDATA", @@ -170,7 +167,8 @@ test "cache directory paths" { { const cache_path = try cache(alloc, .{ .home = mock_home }); defer alloc.free(cache_path); - try testing.expectEqualStrings("/Users/test/Library/Caches", cache_path); + // We don't test the exact path since it comes from NSFileManager + try testing.expect(std.mem.indexOf(u8, cache_path, "Caches") != null); } // Test with subdir @@ -180,7 +178,7 @@ test "cache directory paths" { .subdir = "ghostty", }); defer alloc.free(cache_path); - try testing.expectEqualStrings("/Users/test/Library/Caches/ghostty", cache_path); + try testing.expect(std.mem.indexOf(u8, cache_path, "Caches/ghostty") != null); } }