mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-21 11:16:08 +03:00
Merge pull request #2441 from ghostty-org/push-sqsmtmrsswxp
macos: NSProcessInfo-based arg iterator so CLI args work
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