1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
/* pty spawn. Hand-rolled rather than forkpty() so we need no libutil and
* behave identically under musl. */
#define _GNU_SOURCE
#include "slosh.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 <time.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, uint16_t cell_w, uint16_t cell_h) {
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;
}
/* The pixel fields matter: a program drawing images asks the tty how big a
* cell is, and a winsize that says zero is a program that cannot size an
* image. They are the *terminal's* pixels, so they are cols/rows times the
* cell size the client reported. */
struct winsize ws = {.ws_col = cols,
.ws_row = rows,
.ws_xpixel = (unsigned short)(cols * cell_w),
.ws_ypixel = (unsigned short)(rows * cell_h)};
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);
/* Darwin attaches no line discipline to the master until the slave is
* opened, so the TIOCSWINSZ above returns ENOTTY there and the size is
* dropped: the pane starts 0x0 and stays that way until something else
* resizes it. Setting it here, on the slave we just opened, works on both. */
ioctl(slave, TIOCSWINSZ, &ws);
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("SLOSH", "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, uint16_t cell_w,
uint16_t cell_h) {
struct winsize ws = {.ws_col = cols,
.ws_row = rows,
.ws_xpixel = (unsigned short)(cols * cell_w),
.ws_ypixel = (unsigned short)(rows * cell_h)};
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);
/* Collect it, so a session that opens and closes panes all day does not
* accumulate zombies. Bounded and short: almost everything is gone by the
* first pass, and a program that outlives a SIGHUP is a bigger problem
* than the process-table entry it leaves behind. */
for (int i = 0; i < 5; i++) {
if (waitpid(p->pid, NULL, WNOHANG) != 0) break;
nanosleep(&(struct timespec){0, 200000}, NULL);
}
}
p->pid = -1;
}
|