mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-20 18:56:08 +03:00
Merge pull request #2797 from kyswtn/application-support-dir
Support loading config from "Application Support" directory on macOS
This commit is contained in:
@ -724,7 +724,7 @@ pub const Surface = struct {
|
|||||||
/// Set the shape of the cursor.
|
/// Set the shape of the cursor.
|
||||||
fn setMouseShape(self: *Surface, shape: terminal.MouseShape) !void {
|
fn setMouseShape(self: *Surface, shape: terminal.MouseShape) !void {
|
||||||
if ((comptime builtin.target.isDarwin()) and
|
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
|
// 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
|
// macOS version is >= 13 (Ventura). On prior versions, glfw crashes
|
||||||
|
@ -14,6 +14,7 @@ const std = @import("std");
|
|||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
const build_config = @import("../../build_config.zig");
|
||||||
const apprt = @import("../../apprt.zig");
|
const apprt = @import("../../apprt.zig");
|
||||||
const configpkg = @import("../../config.zig");
|
const configpkg = @import("../../config.zig");
|
||||||
const input = @import("../../input.zig");
|
const input = @import("../../input.zig");
|
||||||
@ -181,7 +182,7 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const default_id = "com.mitchellh.ghostty";
|
const default_id = comptime build_config.bundle_id;
|
||||||
break :app_id if (builtin.mode == .Debug) default_id ++ "-debug" else default_id;
|
break :app_id if (builtin.mode == .Debug) default_id ++ "-debug" else default_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ const ConfigErrors = @This();
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const build_config = @import("../../build_config.zig");
|
||||||
const configpkg = @import("../../config.zig");
|
const configpkg = @import("../../config.zig");
|
||||||
const Config = configpkg.Config;
|
const Config = configpkg.Config;
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ fn init(self: *ConfigErrors, app: *App) !void {
|
|||||||
c.gtk_window_set_title(gtk_window, "Configuration Errors");
|
c.gtk_window_set_title(gtk_window, "Configuration Errors");
|
||||||
c.gtk_window_set_default_size(gtk_window, 600, 275);
|
c.gtk_window_set_default_size(gtk_window, 600, 275);
|
||||||
c.gtk_window_set_resizable(gtk_window, 0);
|
c.gtk_window_set_resizable(gtk_window, 0);
|
||||||
c.gtk_window_set_icon_name(gtk_window, "com.mitchellh.ghostty");
|
c.gtk_window_set_icon_name(gtk_window, build_config.bundle_id);
|
||||||
_ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
||||||
|
|
||||||
// Set some state
|
// Set some state
|
||||||
|
@ -5,6 +5,7 @@ const Surface = @This();
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const build_config = @import("../../build_config.zig");
|
||||||
const configpkg = @import("../../config.zig");
|
const configpkg = @import("../../config.zig");
|
||||||
const apprt = @import("../../apprt.zig");
|
const apprt = @import("../../apprt.zig");
|
||||||
const font = @import("../../font/main.zig");
|
const font = @import("../../font/main.zig");
|
||||||
@ -1149,7 +1150,7 @@ pub fn showDesktopNotification(
|
|||||||
defer c.g_object_unref(notification);
|
defer c.g_object_unref(notification);
|
||||||
c.g_notification_set_body(notification, body.ptr);
|
c.g_notification_set_body(notification, body.ptr);
|
||||||
|
|
||||||
const icon = c.g_themed_icon_new("com.mitchellh.ghostty");
|
const icon = c.g_themed_icon_new(build_config.bundle_id);
|
||||||
defer c.g_object_unref(icon);
|
defer c.g_object_unref(icon);
|
||||||
c.g_notification_set_icon(notification, icon);
|
c.g_notification_set_icon(notification, icon);
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
// to disable this so that terminal programs can capture F10 (such as htop)
|
// to disable this so that terminal programs can capture F10 (such as htop)
|
||||||
c.gtk_window_set_handle_menubar_accel(gtk_window, 0);
|
c.gtk_window_set_handle_menubar_accel(gtk_window, 0);
|
||||||
|
|
||||||
c.gtk_window_set_icon_name(gtk_window, "com.mitchellh.ghostty");
|
c.gtk_window_set_icon_name(gtk_window, build_config.bundle_id);
|
||||||
|
|
||||||
// Apply class to color headerbar if window-theme is set to `ghostty` and
|
// Apply class to color headerbar if window-theme is set to `ghostty` and
|
||||||
// GTK version is before 4.16. The conditional is because above 4.16
|
// GTK version is before 4.16. The conditional is because above 4.16
|
||||||
|
@ -2,6 +2,7 @@ const std = @import("std");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
const build_config = @import("../../build_config.zig");
|
||||||
const App = @import("App.zig");
|
const App = @import("App.zig");
|
||||||
const Surface = @import("Surface.zig");
|
const Surface = @import("Surface.zig");
|
||||||
const TerminalWindow = @import("Window.zig");
|
const TerminalWindow = @import("Window.zig");
|
||||||
@ -141,7 +142,7 @@ const Window = struct {
|
|||||||
self.window = gtk_window;
|
self.window = gtk_window;
|
||||||
c.gtk_window_set_title(gtk_window, "Ghostty: Terminal Inspector");
|
c.gtk_window_set_title(gtk_window, "Ghostty: Terminal Inspector");
|
||||||
c.gtk_window_set_default_size(gtk_window, 1000, 600);
|
c.gtk_window_set_default_size(gtk_window, 1000, 600);
|
||||||
c.gtk_window_set_icon_name(gtk_window, "com.mitchellh.ghostty");
|
c.gtk_window_set_icon_name(gtk_window, build_config.bundle_id);
|
||||||
|
|
||||||
// Initialize our imgui widget
|
// Initialize our imgui widget
|
||||||
try self.imgui_widget.init();
|
try self.imgui_widget.init();
|
||||||
|
@ -103,6 +103,20 @@ pub const app_runtime: apprt.Runtime = config.app_runtime;
|
|||||||
pub const font_backend: font.Backend = config.font_backend;
|
pub const font_backend: font.Backend = config.font_backend;
|
||||||
pub const renderer: rendererpkg.Impl = config.renderer;
|
pub const renderer: rendererpkg.Impl = config.renderer;
|
||||||
|
|
||||||
|
/// The bundle ID for the app. This is used in many places and is currently
|
||||||
|
/// hardcoded here. We could make this configurable in the future if there
|
||||||
|
/// is a reason to do so.
|
||||||
|
///
|
||||||
|
/// On macOS, this must match the App bundle ID. We can get that dynamically
|
||||||
|
/// via an API but I don't want to pay the cost of that at runtime.
|
||||||
|
///
|
||||||
|
/// On GTK, this should match the various folders with resources.
|
||||||
|
///
|
||||||
|
/// There are many places that don't use this variable so simply swapping
|
||||||
|
/// this variable is NOT ENOUGH to change the bundle ID. I just wanted to
|
||||||
|
/// avoid it in Zig coe as much as possible.
|
||||||
|
pub const bundle_id = "com.mitchellh.ghostty";
|
||||||
|
|
||||||
/// True if we should have "slow" runtime safety checks. The initial motivation
|
/// True if we should have "slow" runtime safety checks. The initial motivation
|
||||||
/// for this was terminal page/pagelist integrity checks. These were VERY
|
/// for this was terminal page/pagelist integrity checks. These were VERY
|
||||||
/// slow but very thorough. But they made it so slow that the terminal couldn't
|
/// slow but very thorough. But they made it so slow that the terminal couldn't
|
||||||
|
@ -1813,9 +1813,10 @@ pub fn deinit(self: *Config) void {
|
|||||||
/// Load the configuration according to the default rules:
|
/// Load the configuration according to the default rules:
|
||||||
///
|
///
|
||||||
/// 1. Defaults
|
/// 1. Defaults
|
||||||
/// 2. XDG Config File
|
/// 2. XDG config dir
|
||||||
/// 3. CLI flags
|
/// 3. "Application Support" directory (macOS only)
|
||||||
/// 4. Recursively defined configuration files
|
/// 4. CLI flags
|
||||||
|
/// 5. Recursively defined configuration files
|
||||||
///
|
///
|
||||||
pub fn load(alloc_gpa: Allocator) !Config {
|
pub fn load(alloc_gpa: Allocator) !Config {
|
||||||
var result = try default(alloc_gpa);
|
var result = try default(alloc_gpa);
|
||||||
@ -2398,25 +2399,37 @@ pub fn loadFile(self: *Config, alloc: Allocator, path: []const u8) !void {
|
|||||||
try self.expandPaths(std.fs.path.dirname(path).?);
|
try self.expandPaths(std.fs.path.dirname(path).?);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load the configuration from the default configuration file. The default
|
/// Load optional configuration file from `path`. All errors are ignored.
|
||||||
/// configuration file is at `$XDG_CONFIG_HOME/ghostty/config`.
|
pub fn loadOptionalFile(self: *Config, alloc: Allocator, path: []const u8) void {
|
||||||
pub fn loadDefaultFiles(self: *Config, alloc: Allocator) !void {
|
self.loadFile(alloc, path) catch |err| switch (err) {
|
||||||
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) {
|
|
||||||
error.FileNotFound => std.log.info(
|
error.FileNotFound => std.log.info(
|
||||||
"homedir config not found, not loading path={s}",
|
"optional config file not found, not loading path={s}",
|
||||||
.{config_path},
|
.{path},
|
||||||
),
|
),
|
||||||
|
|
||||||
else => std.log.warn(
|
else => std.log.warn(
|
||||||
"error reading config file, not loading err={} path={s}",
|
"error reading optional config file, not loading err={} path={s}",
|
||||||
.{ err, config_path },
|
.{ 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 (comptime builtin.os.tag == .macos) {
|
||||||
|
const app_support_path = try internal_os.macos.appSupportDir(alloc, "config");
|
||||||
|
defer alloc.free(app_support_path);
|
||||||
|
self.loadOptionalFile(alloc, app_support_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Load and parse the CLI args.
|
/// Load and parse the CLI args.
|
||||||
pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void {
|
pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void {
|
||||||
switch (builtin.os.tag) {
|
switch (builtin.os.tag) {
|
||||||
|
@ -141,7 +141,7 @@ fn logFn(
|
|||||||
|
|
||||||
// Initialize a logger. This is slow to do on every operation
|
// Initialize a logger. This is slow to do on every operation
|
||||||
// but we shouldn't be logging too much.
|
// but we shouldn't be logging too much.
|
||||||
const logger = macos.os.Log.create("com.mitchellh.ghostty", @tagName(scope));
|
const logger = macos.os.Log.create(build_config.bundle_id, @tagName(scope));
|
||||||
defer logger.release();
|
defer logger.release();
|
||||||
logger.log(std.heap.c_allocator, mac_level, format, args);
|
logger.log(std.heap.c_allocator, mac_level, format, args);
|
||||||
}
|
}
|
||||||
|
77
src/os/macos.zig
Normal file
77
src/os/macos.zig
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const build_config = @import("../build_config.zig");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const objc = @import("objc");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
/// Verifies that the running macOS system version is at least the given version.
|
||||||
|
pub fn isAtLeastVersion(major: i64, minor: i64, patch: i64) bool {
|
||||||
|
comptime 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 AppSupportDirError = Allocator.Error || error{AppleAPIFailed};
|
||||||
|
|
||||||
|
/// Return the path to the application support directory for Ghostty
|
||||||
|
/// with the given sub path joined. This allocates the result using the
|
||||||
|
/// given allocator.
|
||||||
|
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,
|
||||||
|
build_config.bundle_id,
|
||||||
|
sub_path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const NSOperatingSystemVersion = extern struct {
|
||||||
|
major: i64,
|
||||||
|
minor: i64,
|
||||||
|
patch: i64,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NSSearchPathDirectory = enum(c_ulong) {
|
||||||
|
NSApplicationSupportDirectory = 14,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NSSearchPathDomainMask = enum(c_ulong) {
|
||||||
|
NSUserDomainMask = 1,
|
||||||
|
};
|
@ -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,
|
|
||||||
};
|
|
@ -8,7 +8,6 @@ const file = @import("file.zig");
|
|||||||
const flatpak = @import("flatpak.zig");
|
const flatpak = @import("flatpak.zig");
|
||||||
const homedir = @import("homedir.zig");
|
const homedir = @import("homedir.zig");
|
||||||
const locale = @import("locale.zig");
|
const locale = @import("locale.zig");
|
||||||
const macos_version = @import("macos_version.zig");
|
|
||||||
const mouse = @import("mouse.zig");
|
const mouse = @import("mouse.zig");
|
||||||
const openpkg = @import("open.zig");
|
const openpkg = @import("open.zig");
|
||||||
const pipepkg = @import("pipe.zig");
|
const pipepkg = @import("pipe.zig");
|
||||||
@ -21,6 +20,7 @@ pub const hostname = @import("hostname.zig");
|
|||||||
pub const passwd = @import("passwd.zig");
|
pub const passwd = @import("passwd.zig");
|
||||||
pub const xdg = @import("xdg.zig");
|
pub const xdg = @import("xdg.zig");
|
||||||
pub const windows = @import("windows.zig");
|
pub const windows = @import("windows.zig");
|
||||||
|
pub const macos = @import("macos.zig");
|
||||||
|
|
||||||
// Functions and types
|
// Functions and types
|
||||||
pub const CFReleaseThread = @import("cf_release_thread.zig");
|
pub const CFReleaseThread = @import("cf_release_thread.zig");
|
||||||
@ -37,7 +37,6 @@ pub const freeTmpDir = file.freeTmpDir;
|
|||||||
pub const isFlatpak = flatpak.isFlatpak;
|
pub const isFlatpak = flatpak.isFlatpak;
|
||||||
pub const home = homedir.home;
|
pub const home = homedir.home;
|
||||||
pub const ensureLocale = locale.ensureLocale;
|
pub const ensureLocale = locale.ensureLocale;
|
||||||
pub const macosVersionAtLeast = macos_version.macosVersionAtLeast;
|
|
||||||
pub const clickInterval = mouse.clickInterval;
|
pub const clickInterval = mouse.clickInterval;
|
||||||
pub const open = openpkg.open;
|
pub const open = openpkg.open;
|
||||||
pub const pipe = pipepkg.pipe;
|
pub const pipe = pipepkg.pipe;
|
||||||
|
Reference in New Issue
Block a user