Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131const std = @import("std");const builtin = @import("builtin");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.ArgIteratorpub 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);}