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
|
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const objc = @import("objc");
const macos = @import("macos");
/// Returns an iterator over the command line arguments. This may or may
/// not allocate depending on the platform.
///
/// For Zig-aware readers: this is the same as std.process.argsWithAllocator
/// but handles macOS using NSProcessInfo instead of libc argc/argv.
pub fn iterator(allocator: Allocator) ArgIterator.InitError!ArgIterator {
//if (true) return try std.process.argsWithAllocator(allocator);
return .initWithAllocator(allocator);
}
/// Duck-typed to std.process.ArgIterator
pub const ArgIterator = switch (builtin.os.tag) {
.macos => IteratorMacOS,
else => std.process.ArgIterator,
};
/// This is an ArgIterator (duck-typed for std.process.ArgIterator) for
/// NSApplicationMain-based applications on macOS. It uses NSProcessInfo to
/// get the command line arguments since libc argc/argv pointers are not
/// valid.
///
/// I believe this should work for all macOS applications even if
/// NSApplicationMain is not used, but I haven't tested that so I'm not
/// sure. If/when libghostty is ever used outside of NSApplicationMain
/// then we can revisit this.
const IteratorMacOS = struct {
alloc: Allocator,
index: usize,
count: usize,
buf: [:0]u8,
args: objc.Object,
pub const InitError = Allocator.Error;
pub fn initWithAllocator(alloc: Allocator) InitError!IteratorMacOS {
const NSProcessInfo = objc.getClass("NSProcessInfo").?;
const info = NSProcessInfo.msgSend(objc.Object, objc.sel("processInfo"), .{});
const args = info.getProperty(objc.Object, "arguments");
errdefer args.release();
// Determine our maximum length so we can allocate the buffer to
// fit all values.
var max: usize = 0;
const count: usize = @intCast(args.getProperty(c_ulong, "count"));
for (0..count) |i| {
const nsstr = args.msgSend(
objc.Object,
objc.sel("objectAtIndex:"),
.{@as(c_ulong, @intCast(i))},
);
const maxlen: usize = @intCast(nsstr.msgSend(
c_ulong,
objc.sel("maximumLengthOfBytesUsingEncoding:"),
.{@as(c_ulong, 4)},
));
max = @max(max, maxlen);
}
// Allocate our buffer. We add 1 for the null terminator.
const buf = try alloc.allocSentinel(u8, max, 0);
errdefer alloc.free(buf);
return .{
.alloc = alloc,
.index = 0,
.count = count,
.buf = buf,
.args = args,
};
}
pub fn deinit(self: *IteratorMacOS) void {
self.alloc.free(self.buf);
// Note: we don't release self.args because it is a pointer copy
// not a retained object.
}
pub fn next(self: *IteratorMacOS) ?[:0]const u8 {
if (self.index == self.count) return null;
// NSString. No release because not a copy.
const nsstr = self.args.msgSend(
objc.Object,
objc.sel("objectAtIndex:"),
.{@as(c_ulong, @intCast(self.index))},
);
self.index += 1;
// Convert to string using getCString. Our buffer should always
// be big enough because we precomputed the maximum length.
if (!nsstr.msgSend(
bool,
objc.sel("getCString:maxLength:encoding:"),
.{
@as([*]u8, @ptrCast(self.buf.ptr)),
@as(c_ulong, @intCast(self.buf.len)),
@as(c_ulong, 4), // NSUTF8StringEncoding
},
)) {
// This should never happen... if it does, we just return empty.
return "";
}
return std.mem.sliceTo(self.buf, 0);
}
pub fn skip(self: *IteratorMacOS) bool {
if (self.index == self.count) return false;
self.index += 1;
return true;
}
};
test "args" {
const testing = std.testing;
const alloc = testing.allocator;
var iter = try iterator(alloc);
defer iter.deinit();
try testing.expect(iter.next().?.len > 0);
}
|