diff options
| author | sl0p <sl0p@sl0p.foo> | 2026-08-13 22:00:03 +0200 |
|---|---|---|
| committer | sl0p <sl0p@sl0p.foo> | 2026-08-13 22:00:03 +0200 |
| commit | 2c1b855c3cf8cf8841bf25cd939e478a7f0bfac8 (patch) | |
| tree | 7cd6274303ba1da1f8bd57371aec336dd687fb58 /src/pty.c | |
| download | slosh-2c1b855c3cf8cf8841bf25cd939e478a7f0bfac8.tar.gz slosh-2c1b855c3cf8cf8841bf25cd939e478a7f0bfac8.tar.xz slosh-2c1b855c3cf8cf8841bf25cd939e478a7f0bfac8.zip | |
sl0ptty: M0 — libghostty-vt vendored, one pane, compositor, diff renderer
Design contract in DESIGN.md. libghostty-vt vendored fresh from ghostty
main@bedb6f2 (zig 0.16), built to a 14MB static lib.
M0: pty spawn (no libutil), a lib-vt terminal per pane, a screen cell buffer,
a diff emitter that repaints only what changed, raw mode, SIGWINCH, and a
~120Hz frame cap that coalesces pty reads.
Also the seed of the headless harness: --headless dumps the composited screen
without a tty, so composition is testable in milliseconds.
tests/live_m0.py covers what headless cannot: raw mode, the diff emitter,
resize propagation, and the quit path. 6/6 green.
Diffstat (limited to 'src/pty.c')
| -rw-r--r-- | src/pty.c | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/src/pty.c b/src/pty.c new file mode 100644 index 0000000..5ce166e --- /dev/null +++ b/src/pty.c @@ -0,0 +1,86 @@ +/* pty spawn. Hand-rolled rather than forkpty() so we need no libutil and + * behave identically under musl. */ +#define _GNU_SOURCE +#include "sl0ptty.h" + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <termios.h> +#include <unistd.h> + +int pty_spawn(pty_t *p, const char *const argv[], uint16_t cols, uint16_t rows, + const char *cwd) { + int master = posix_openpt(O_RDWR | O_NOCTTY); + if (master < 0) return -1; + if (grantpt(master) < 0 || unlockpt(master) < 0) { + close(master); + return -1; + } + + char slave_name[128]; + if (ptsname_r(master, slave_name, sizeof slave_name) != 0) { + close(master); + return -1; + } + + struct winsize ws = {.ws_col = cols, .ws_row = rows}; + ioctl(master, TIOCSWINSZ, &ws); + + pid_t pid = fork(); + if (pid < 0) { + close(master); + return -1; + } + + if (pid == 0) { + /* child: new session, pty becomes the controlling terminal */ + setsid(); + int slave = open(slave_name, O_RDWR); + if (slave < 0) _exit(127); + ioctl(slave, TIOCSCTTY, 0); + dup2(slave, STDIN_FILENO); + dup2(slave, STDOUT_FILENO); + dup2(slave, STDERR_FILENO); + if (slave > STDERR_FILENO) close(slave); + close(master); + + if (cwd) { if (chdir(cwd) != 0) { /* keep going in the old cwd */ } } + + /* We are opinionated about the outer terminal (D11), and we present the + * same contract inward. xterm-ghostty is what libghostty-vt implements. */ + setenv("TERM", "xterm-ghostty", 1); + setenv("SL0PTTY", "1", 1); + unsetenv("ZELLIJ"); + + signal(SIGPIPE, SIG_DFL); + execvp(argv[0], (char *const *)argv); + _exit(127); + } + + /* parent */ + int flags = fcntl(master, F_GETFL, 0); + fcntl(master, F_SETFL, flags | O_NONBLOCK); + p->fd = master; + p->pid = pid; + return 0; +} + +int pty_resize(pty_t *p, uint16_t cols, uint16_t rows) { + struct winsize ws = {.ws_col = cols, .ws_row = rows}; + return ioctl(p->fd, TIOCSWINSZ, &ws); +} + +void pty_close(pty_t *p) { + if (p->fd >= 0) close(p->fd); + p->fd = -1; + if (p->pid > 0) { + kill(p->pid, SIGHUP); + waitpid(p->pid, NULL, WNOHANG); + } + p->pid = -1; +} |
