1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
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.os.tag.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![]const u8 {
return try commonDir(
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![]const u8 {
return try commonDir(
alloc,
.NSCachesDirectory,
&.{ build_config.bundle_id, 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
// target.
ThreadIncompatible,
};
/// Set the QoS class of the running thread.
///
/// https://developer.apple.com/documentation/apple-silicon/tuning-your-code-s-performance-for-apple-silicon?preferredLanguage=occ
pub fn setQosClass(class: QosClass) !void {
return switch (std.posix.errno(pthread_set_qos_class_self_np(
class,
0,
))) {
.SUCCESS => {},
.PERM => error.ThreadIncompatible,
// EPERM is the only known error that can happen based on
// the man pages for pthread_set_qos_class_self_np. I haven't
// checked the XNU source code to see if there are other
// possible errors.
else => @panic("unexpected pthread_set_qos_class_self_np error"),
};
}
/// https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/PrioritizeWorkAtTheTaskLevel.html#//apple_ref/doc/uid/TP40013929-CH35-SW1
pub const QosClass = enum(c_uint) {
user_interactive = 0x21,
user_initiated = 0x19,
default = 0x15,
utility = 0x11,
background = 0x09,
unspecified = 0x00,
};
extern "c" fn pthread_set_qos_class_self_np(
qos_class: QosClass,
relative_priority: c_int,
) c_int;
pub extern "c" fn pthread_setname_np(
name: [*:0]const u8,
) void;
pub const NSOperatingSystemVersion = extern struct {
major: i64,
minor: i64,
patch: i64,
};
pub const NSSearchPathDirectory = enum(c_ulong) {
NSCachesDirectory = 13,
NSApplicationSupportDirectory = 14,
};
pub const NSSearchPathDomainMask = enum(c_ulong) {
NSUserDomainMask = 1,
};
fn commonDir(
alloc: Allocator,
directory: NSSearchPathDirectory,
sub_paths: []const []const u8,
) (error{AppleAPIFailed} || Allocator.Error)![]const u8 {
comptime assert(builtin.target.os.tag.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);
}
test "cacheDir paths" {
if (!builtin.target.os.tag.isDarwin()) return;
const testing = std.testing;
const alloc = testing.allocator;
// Test base path
{
const cache_path = try cacheDir(alloc, "");
defer alloc.free(cache_path);
try testing.expect(std.mem.indexOf(u8, cache_path, "Caches") != null);
try testing.expect(std.mem.indexOf(u8, cache_path, build_config.bundle_id) != null);
}
// Test with subdir
{
const cache_path = try cacheDir(alloc, "test");
defer alloc.free(cache_path);
try testing.expect(std.mem.indexOf(u8, cache_path, "Caches") != null);
try testing.expect(std.mem.indexOf(u8, cache_path, build_config.bundle_id) != null);
const bundle_path = try std.fmt.allocPrint(alloc, "{s}/test", .{build_config.bundle_id});
defer alloc.free(bundle_path);
try testing.expect(std.mem.indexOf(u8, cache_path, bundle_path) != null);
}
}
|