From 10e37a3deefcb3e433f4dfa77336cafdd363edc2 Mon Sep 17 00:00:00 2001 From: Kyaw Date: Sun, 24 Nov 2024 17:08:07 +0700 Subject: [PATCH] config: support loading from "Application Support" directory on macOS --- src/apprt/glfw.zig | 2 +- src/config/Config.zig | 43 ++++++++++++++++++++++---------- src/os/macos.zig | 54 ++++++++++++++++++++++++++++++++++++++++ src/os/macos_version.zig | 21 ---------------- src/os/main.zig | 3 +-- 5 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 src/os/macos.zig delete mode 100644 src/os/macos_version.zig diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index e793615d5..bf4c44ad0 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -724,7 +724,7 @@ pub const Surface = struct { /// Set the shape of the cursor. fn setMouseShape(self: *Surface, shape: terminal.MouseShape) !void { if ((comptime builtin.target.isDarwin()) and - !internal_os.macosVersionAtLeast(13, 0, 0)) + !internal_os.macos.isAtLeastVersion(13, 0, 0)) { // We only set our cursor if we're NOT on Mac, or if we are then the // macOS version is >= 13 (Ventura). On prior versions, glfw crashes diff --git a/src/config/Config.zig b/src/config/Config.zig index 55cd55606..e2843508a 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1809,10 +1809,15 @@ pub fn deinit(self: *Config) void { /// Load the configuration according to the default rules: /// /// 1. Defaults -/// 2. XDG Config File +/// 2. Configuration Files /// 3. CLI flags /// 4. Recursively defined configuration files /// +/// Configuration files are loaded in the follow order: +/// +/// 1. XDG Config File +/// 2. "Application Support" Config File on macOS +/// pub fn load(alloc_gpa: Allocator) !Config { var result = try default(alloc_gpa); errdefer result.deinit(); @@ -2394,25 +2399,37 @@ pub fn loadFile(self: *Config, alloc: Allocator, path: []const u8) !void { try self.expandPaths(std.fs.path.dirname(path).?); } -/// Load the configuration from the default configuration file. The default -/// configuration file is at `$XDG_CONFIG_HOME/ghostty/config`. -pub fn loadDefaultFiles(self: *Config, alloc: Allocator) !void { - const config_path = try internal_os.xdg.config(alloc, .{ .subdir = "ghostty/config" }); - defer alloc.free(config_path); - - self.loadFile(alloc, config_path) catch |err| switch (err) { +/// Load optional configuration file from `path`. All errors are ignored. +pub fn loadOptionalFile(self: *Config, alloc: Allocator, path: []const u8) void { + self.loadFile(alloc, path) catch |err| switch (err) { error.FileNotFound => std.log.info( - "homedir config not found, not loading path={s}", - .{config_path}, + "optional config file not found, not loading path={s}", + .{path}, ), - else => std.log.warn( - "error reading config file, not loading err={} path={s}", - .{ err, config_path }, + "error reading optional config file, not loading err={} path={s}", + .{ err, path }, ), }; } +/// Load configurations from the default configuration files. The default +/// configuration file is at `$XDG_CONFIG_HOME/ghostty/config`. +/// +/// On macOS, `$HOME/Library/Application Support/$CFBundleIdentifier/config` +/// is also loaded. +pub fn loadDefaultFiles(self: *Config, alloc: Allocator) !void { + const xdg_path = try internal_os.xdg.config(alloc, .{ .subdir = "ghostty/config" }); + defer alloc.free(xdg_path); + self.loadOptionalFile(alloc, xdg_path); + + if (builtin.os.tag == .macos) { + const app_support_path = try internal_os.macos.getAppSupportDir(alloc, "config"); + defer alloc.free(app_support_path); + self.loadOptionalFile(alloc, app_support_path); + } +} + /// Load and parse the CLI args. pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void { switch (builtin.os.tag) { diff --git a/src/os/macos.zig b/src/os/macos.zig new file mode 100644 index 000000000..62ba87c5a --- /dev/null +++ b/src/os/macos.zig @@ -0,0 +1,54 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const objc = @import("objc"); +const Allocator = std.mem.Allocator; + +pub const NSOperatingSystemVersion = extern struct { + major: i64, + minor: i64, + patch: i64, +}; + +/// Verifies that the running macOS system version is at least the given version. +pub fn isAtLeastVersion(major: i64, minor: i64, patch: i64) bool { + assert(builtin.target.isDarwin()); + + const NSProcessInfo = objc.getClass("NSProcessInfo").?; + const info = NSProcessInfo.msgSend(objc.Object, objc.sel("processInfo"), .{}); + return info.msgSend(bool, objc.sel("isOperatingSystemAtLeastVersion:"), .{ + NSOperatingSystemVersion{ .major = major, .minor = minor, .patch = patch }, + }); +} + +pub const NSSearchPathDirectory = enum(c_ulong) { + NSApplicationSupportDirectory = 14, +}; + +pub const NSSearchPathDomainMask = enum(c_ulong) { + NSUserDomainMask = 1, +}; + +pub fn getAppSupportDir(alloc: Allocator, sub_path: []const u8) ![]u8 { + assert(builtin.target.isDarwin()); + + const err: ?*anyopaque = undefined; + 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, + &err, + }, + ); + const path = url.getProperty(objc.Object, "path"); + const c_str = path.getProperty([*:0]const u8, "UTF8String"); + const app_support_dir = std.mem.sliceTo(c_str, 0); + + return try std.fs.path.join(alloc, &.{ app_support_dir, "com.mitchellh.ghostty", sub_path }); +} diff --git a/src/os/macos_version.zig b/src/os/macos_version.zig deleted file mode 100644 index e0b21560e..000000000 --- a/src/os/macos_version.zig +++ /dev/null @@ -1,21 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const objc = @import("objc"); - -/// Verifies that the running macOS system version is at least the given version. -pub fn macosVersionAtLeast(major: i64, minor: i64, patch: i64) bool { - assert(builtin.target.isDarwin()); - - const NSProcessInfo = objc.getClass("NSProcessInfo").?; - const info = NSProcessInfo.msgSend(objc.Object, objc.sel("processInfo"), .{}); - return info.msgSend(bool, objc.sel("isOperatingSystemAtLeastVersion:"), .{ - NSOperatingSystemVersion{ .major = major, .minor = minor, .patch = patch }, - }); -} - -pub const NSOperatingSystemVersion = extern struct { - major: i64, - minor: i64, - patch: i64, -}; diff --git a/src/os/main.zig b/src/os/main.zig index 22765f546..073129300 100644 --- a/src/os/main.zig +++ b/src/os/main.zig @@ -8,7 +8,6 @@ const file = @import("file.zig"); const flatpak = @import("flatpak.zig"); const homedir = @import("homedir.zig"); const locale = @import("locale.zig"); -const macos_version = @import("macos_version.zig"); const mouse = @import("mouse.zig"); const openpkg = @import("open.zig"); const pipepkg = @import("pipe.zig"); @@ -21,6 +20,7 @@ pub const hostname = @import("hostname.zig"); pub const passwd = @import("passwd.zig"); pub const xdg = @import("xdg.zig"); pub const windows = @import("windows.zig"); +pub const macos = @import("macos.zig"); // Functions and types pub const CFReleaseThread = @import("cf_release_thread.zig"); @@ -37,7 +37,6 @@ pub const freeTmpDir = file.freeTmpDir; pub const isFlatpak = flatpak.isFlatpak; pub const home = homedir.home; pub const ensureLocale = locale.ensureLocale; -pub const macosVersionAtLeast = macos_version.macosVersionAtLeast; pub const clickInterval = mouse.clickInterval; pub const open = openpkg.open; pub const pipe = pipepkg.pipe;