repos / zmx

session persistence for terminal processes
git clone https://github.com/neurosnap/zmx.git

zmx / src
Eric Bower  ·  2026-04-24

cross.zig

 1const builtin = @import("builtin");
 2const std = @import("std");
 3const posix = std.posix;
 4
 5pub const c = switch (builtin.os.tag) {
 6    .macos => @cImport({
 7        @cInclude("sys/ioctl.h"); // ioctl and constants
 8        @cInclude("sys/sysctl.h"); // sysctl for process name lookup
 9        @cInclude("termios.h");
10        @cInclude("stdlib.h");
11        @cInclude("unistd.h");
12    }),
13    .freebsd => @cImport({
14        @cInclude("termios.h"); // ioctl and constants
15        @cInclude("libutil.h"); // openpty()
16        @cInclude("stdlib.h");
17        @cInclude("unistd.h");
18    }),
19    else => @cImport({
20        @cInclude("sys/ioctl.h"); // ioctl and constants
21        @cInclude("pty.h");
22        @cInclude("stdlib.h");
23        @cInclude("unistd.h");
24    }),
25};
26
27// Manually declare forkpty for macOS since util.h is not available during cross-compilation
28pub const forkpty = if (builtin.os.tag == .macos)
29    struct {
30        extern "c" fn forkpty(master_fd: *c_int, name: ?[*:0]u8, termp: ?*const c.struct_termios, winp: ?*const c.struct_winsize) c_int;
31    }.forkpty
32else
33    c.forkpty;
34
35/// Returns the basename of the foreground process running on the given PTY fd.
36/// Writes into `buf` and returns a slice of it, or null on failure.
37pub fn getForegroundProcessName(pty_fd: i32, buf: []u8) ?[]const u8 {
38    const pgid = c.tcgetpgrp(pty_fd);
39    if (pgid <= 0) return null;
40
41    switch (builtin.os.tag) {
42        .macos => {
43            // Use KERN_PROC_PGRP to find the process in the foreground group.
44            // We walk the process list and find the first process whose pgid matches.
45            var mib = [_]c_int{ c.CTL_KERN, c.KERN_PROC, c.KERN_PROC_PGRP, @intCast(pgid) };
46            var size: usize = 0;
47            if (c.sysctl(&mib, mib.len, null, &size, null, 0) != 0) return null;
48            if (size == 0) return null;
49
50            // kinfo_proc is large; allocate on heap to avoid blowing the stack
51            const kinfo_size = @sizeOf(c.struct_kinfo_proc);
52            const count = size / kinfo_size;
53            if (count == 0) return null;
54
55            // Use a stack buffer for small lists (usually 1-3 procs), heap otherwise.
56            var stack_buf: [8 * @sizeOf(c.struct_kinfo_proc)]u8 align(@alignOf(c.struct_kinfo_proc)) = undefined;
57            const heap_needed = size > stack_buf.len;
58            const proc_buf: []u8 = if (heap_needed)
59                std.heap.c_allocator.alloc(u8, size) catch return null
60            else
61                stack_buf[0..size];
62            defer if (heap_needed) std.heap.c_allocator.free(proc_buf);
63
64            if (c.sysctl(&mib, mib.len, proc_buf.ptr, &size, null, 0) != 0) return null;
65
66            const procs: []c.struct_kinfo_proc = @alignCast(std.mem.bytesAsSlice(c.struct_kinfo_proc, proc_buf[0..size]));
67            if (procs.len == 0) return null;
68
69            // p_comm is a null-terminated fixed-length field
70            const comm: [*:0]const u8 = @ptrCast(&procs[0].kp_proc.p_comm);
71            const name = std.mem.sliceTo(comm, 0);
72            const copy_len = @min(name.len, buf.len);
73            @memcpy(buf[0..copy_len], name[0..copy_len]);
74            return buf[0..copy_len];
75        },
76        .linux => {
77            // /proc/<pid>/comm contains just the process name + newline
78            var path_buf: [64]u8 = undefined;
79            const path = std.fmt.bufPrint(&path_buf, "/proc/{d}/comm", .{pgid}) catch return null;
80            const file = std.fs.openFileAbsolute(path, .{}) catch return null;
81            defer file.close();
82            const n = file.read(buf) catch return null;
83            // strip trailing newline
84            const end = if (n > 0 and buf[n - 1] == '\n') n - 1 else n;
85            return buf[0..end];
86        },
87        else => return null,
88    }
89}