Add custom layout creation.

This commit is contained in:
Greyson Parrelli committed 2026-08-11 13:57:54 -04:00
1 parent a7a9429d09
commit 3026bdcd4c
14 files changed
+1964 -20

No files matched your search

+38 -1
View File
@@ -27,6 +27,8 @@ const c = struct {
extern "c" fn ptsname_r(fd: c_int, buf: [*]u8, buflen: usize) c_int;
extern "c" fn setsid() pid_t;
extern "c" fn fork() pid_t;
extern "c" fn chdir(path: [*:0]const u8) c_int;
extern "c" fn readlink(path: [*:0]const u8, buf: [*]u8, bufsiz: usize) isize;
extern "c" fn close(fd: fd_t) c_int;
extern "c" fn dup2(old: fd_t, new: fd_t) c_int;
extern "c" fn open(path: [*:0]const u8, flags: c_int, ...) c_int;
@@ -86,10 +88,15 @@ pub const Error = error{
/// started by running e.g. /usr/bin/zsh with an argv[0] of "-zsh"; folding
/// them together would pass "-zsh" as an ordinary argument instead, which
/// shells either misparse or reject outright.
///
/// `dir` is the directory the child starts in. A null, or a directory that
/// cannot be entered, leaves it wherever the app was started — a layout
/// naming a path that no longer exists should still give you a usable shell.
pub fn create(
alloc: std.mem.Allocator,
path: [:0]const u8,
argv: []const [:0]const u8,
dir: ?[:0]const u8,
size: Winsize,
) !Pty {
const master = c.posix_openpt(O_RDWR | O_NOCTTY);
@@ -120,7 +127,7 @@ pub fn create(
const pid = c.fork();
if (pid < 0) return Error.ForkFailed;
if (pid == 0) {
childExec(master, slave_path_z, path, argv_z, envp_z);
childExec(master, slave_path_z, path, argv_z, envp_z, dir);
// childExec only returns on failure, and a forked child has no
// sensible way to report that back to us.
c._exit(127);
@@ -136,9 +143,17 @@ fn childExec(
path: [:0]const u8,
argv: [:null]const ?[*:0]const u8,
envp: [:null]const ?[*:0]const u8,
dir: ?[:0]const u8,
) void {
_ = c.close(master);
// Deliberately unchecked: a layout pointing at a directory that has since
// been moved or deleted should still open a shell, in the directory the
// app was started from, rather than a pane that dies on arrival.
if (dir) |d| if (d.len > 0) {
_ = c.chdir(d.ptr);
};
// A new session detaches us from the parent's controlling terminal so
// that we can claim the slave as our own below.
if (c.setsid() < 0) return;
@@ -211,6 +226,28 @@ fn freeEnv(alloc: std.mem.Allocator, envp: [:null]?[*:0]const u8) void {
alloc.free(envp);
}
/// The child's current working directory, copied into `buf`, or null if it
/// can't be read.
///
/// This is the shell's directory as of right now, not the one it started in,
/// which is what makes "save this tab as a layout" record where you actually
/// are rather than where you began. Read straight out of procfs: there is no
/// portable API for it, and this app is Linux-only regardless.
pub fn cwd(self: Pty, buf: []u8) ?[]const u8 {
var link_buf: [64]u8 = undefined;
const link = std.fmt.bufPrintZ(&link_buf, "/proc/{d}/cwd", .{self.pid}) catch return null;
const n = c.readlink(link.ptr, buf.ptr, buf.len);
if (n <= 0) return null;
const len: usize = @intCast(n);
// readlink truncates rather than failing, and a truncated path is worse
// than none: it would silently point somewhere else.
if (len >= buf.len) return null;
return buf[0..len];
}
/// Tell the child its window changed size. This both updates the kernel's
/// idea of the terminal size and delivers SIGWINCH to the foreground group.
pub fn setSize(self: Pty, size: Winsize) void {