mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
macos: NSProcessInfo-based arg iterator
Fixes #2432 On macOS, processes with an NSApplicationMain entrypoint do not have access to libc argc/argv. Instead, we must use NSProcessInfo. This commit introduces an args iterator that uses NSProcessInfo, giving us access to the args. This also fixes an issue where we were not properly skipping argv0 when iterating over the args. This happened to be fine because we happened to ignore invalid args but it introduces a config error.
This commit is contained in:
@ -2290,8 +2290,12 @@ pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void {
|
|||||||
switch (builtin.os.tag) {
|
switch (builtin.os.tag) {
|
||||||
.windows => {},
|
.windows => {},
|
||||||
|
|
||||||
// Fast-path if we are non-Windows and no args, do nothing.
|
// Fast-path if we are Linux and have no args.
|
||||||
else => if (std.os.argv.len <= 1) return,
|
.linux => if (std.os.argv.len <= 1) return,
|
||||||
|
|
||||||
|
// Everything else we have to at least try because it may
|
||||||
|
// not use std.os.argv.
|
||||||
|
else => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// On Linux, we have a special case where if the executing
|
// On Linux, we have a special case where if the executing
|
||||||
@ -2360,9 +2364,13 @@ pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void {
|
|||||||
counter[i] = @field(self, field).list.items.len;
|
counter[i] = @field(self, field).list.items.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the config from the CLI args
|
// Initialize our CLI iterator. The first argument is always assumed
|
||||||
var iter = try std.process.argsWithAllocator(alloc_gpa);
|
// to be the program name so we skip over that.
|
||||||
|
var iter = try internal_os.args.iterator(alloc_gpa);
|
||||||
defer iter.deinit();
|
defer iter.deinit();
|
||||||
|
if (iter.next()) |argv0| log.debug("skipping argv0 value={s}", .{argv0});
|
||||||
|
|
||||||
|
// Parse the config from the CLI args
|
||||||
try self.loadIter(alloc_gpa, &iter);
|
try self.loadIter(alloc_gpa, &iter);
|
||||||
|
|
||||||
// If we are not loading the default files, then we need to
|
// If we are not loading the default files, then we need to
|
||||||
|
131
src/os/args.zig
Normal file
131
src/os/args.zig
Normal file
@ -0,0 +1,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 ArgIterator.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);
|
||||||
|
}
|
@ -15,6 +15,7 @@ const pipepkg = @import("pipe.zig");
|
|||||||
const resourcesdir = @import("resourcesdir.zig");
|
const resourcesdir = @import("resourcesdir.zig");
|
||||||
|
|
||||||
// Namespaces
|
// Namespaces
|
||||||
|
pub const args = @import("args.zig");
|
||||||
pub const cgroup = @import("cgroup.zig");
|
pub const cgroup = @import("cgroup.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");
|
||||||
|
Reference in New Issue
Block a user