Line data Source code
1 : /**
2 : * @file pty_session.c
3 : * @brief PTY session lifecycle, input, and screen inspection.
4 : */
5 :
6 : #define _DEFAULT_SOURCE
7 : #define _XOPEN_SOURCE 600
8 :
9 : #include "pty_internal.h"
10 : #include <errno.h>
11 : #include <fcntl.h>
12 : #include <signal.h>
13 : #include <stdio.h>
14 : #include <stdlib.h>
15 : #include <string.h>
16 : #include <unistd.h>
17 : #include <sys/ioctl.h>
18 : #include <sys/wait.h>
19 :
20 : #if defined(__APPLE__)
21 : #include <util.h>
22 : #else
23 : #include <pty.h>
24 : #endif
25 :
26 : /* ── Lifecycle ───────────────────────────────────────────────────────── */
27 :
28 24675 : PtySession *pty_open(int cols, int rows) {
29 24675 : PtySession *s = calloc(1, sizeof(*s));
30 24675 : if (!s) return NULL;
31 24675 : s->master_fd = -1;
32 24675 : s->child_pid = -1;
33 24675 : s->cols = cols;
34 24675 : s->rows = rows;
35 24675 : s->screen = pty_screen_new(cols, rows);
36 24675 : if (!s->screen) { free(s); return NULL; }
37 24675 : return s;
38 : }
39 :
40 24675 : int pty_run(PtySession *s, const char *argv[]) {
41 24675 : struct winsize ws = {
42 24675 : .ws_row = (unsigned short)s->rows,
43 24675 : .ws_col = (unsigned short)s->cols
44 : };
45 :
46 : int master_fd;
47 24675 : pid_t pid = forkpty(&master_fd, NULL, NULL, &ws);
48 24675 : if (pid < 0) return -1;
49 :
50 24675 : if (pid == 0) {
51 : /* Child process */
52 381 : setenv("TERM", "xterm-256color", 1);
53 381 : setenv("LC_ALL", "en_US.UTF-8", 1);
54 : /* Remove COLUMNS/LINES so the program queries the PTY */
55 381 : unsetenv("COLUMNS");
56 381 : unsetenv("LINES");
57 381 : execvp(argv[0], (char *const *)argv);
58 381 : _exit(127);
59 : }
60 :
61 : /* Parent */
62 24294 : s->master_fd = master_fd;
63 24294 : s->child_pid = pid;
64 :
65 : /* Set master fd to non-blocking for poll-based reads */
66 24294 : int flags = fcntl(master_fd, F_GETFL);
67 24294 : if (flags >= 0) fcntl(master_fd, F_SETFL, flags | O_NONBLOCK);
68 :
69 24294 : return 0;
70 : }
71 :
72 24294 : void pty_close(PtySession *s) {
73 24294 : if (!s) return;
74 :
75 24294 : if (s->child_pid > 0) {
76 24294 : kill(s->child_pid, SIGTERM);
77 : /* Give the child 100ms to exit */
78 24294 : usleep(100000);
79 : int status;
80 24294 : if (waitpid(s->child_pid, &status, WNOHANG) == 0) {
81 0 : kill(s->child_pid, SIGKILL);
82 0 : waitpid(s->child_pid, &status, 0);
83 : }
84 : }
85 :
86 24294 : if (s->master_fd >= 0) close(s->master_fd);
87 24294 : pty_screen_free(s->screen);
88 24294 : free(s);
89 : }
90 :
91 : /* ── Input ───────────────────────────────────────────────────────────── */
92 :
93 141134 : void pty_send(PtySession *s, const char *bytes, size_t len) {
94 141134 : if (!s || s->master_fd < 0) return;
95 141134 : ssize_t n = write(s->master_fd, bytes, len);
96 : (void)n;
97 : }
98 :
99 124981 : void pty_send_key(PtySession *s, PtyKey key) {
100 124981 : switch (key) {
101 1217 : case PTY_KEY_UP: pty_send(s, "\033[A", 3); break;
102 59870 : case PTY_KEY_DOWN: pty_send(s, "\033[B", 3); break;
103 262 : case PTY_KEY_RIGHT: pty_send(s, "\033[C", 3); break;
104 172 : case PTY_KEY_LEFT: pty_send(s, "\033[D", 3); break;
105 490 : case PTY_KEY_PGUP: pty_send(s, "\033[5~", 4); break;
106 653 : case PTY_KEY_PGDN: pty_send(s, "\033[6~", 4); break;
107 10451 : case PTY_KEY_HOME: pty_send(s, "\033[H", 3); break;
108 737 : case PTY_KEY_END: pty_send(s, "\033[F", 3); break;
109 51129 : default: {
110 51129 : char c = (char)key;
111 51129 : pty_send(s, &c, 1);
112 : }
113 : }
114 124981 : }
115 :
116 16126 : void pty_send_str(PtySession *s, const char *str) {
117 16126 : if (str) pty_send(s, str, strlen(str));
118 16126 : }
119 :
120 : /* ── Screen inspection ───────────────────────────────────────────────── */
121 :
122 634 : const char *pty_cell_text(PtySession *s, int row, int col) {
123 634 : if (!s || !s->screen) return "";
124 634 : if (row < 0 || row >= s->rows || col < 0 || col >= s->cols) return "";
125 634 : return s->screen->cells[row * s->cols + col].ch;
126 : }
127 :
128 581 : int pty_cell_attr(PtySession *s, int row, int col) {
129 581 : if (!s || !s->screen) return 0;
130 581 : if (row < 0 || row >= s->rows || col < 0 || col >= s->cols) return 0;
131 581 : return s->screen->cells[row * s->cols + col].attr;
132 : }
133 :
134 284 : int pty_cell_fg(PtySession *s, int row, int col) {
135 284 : if (!s || !s->screen) return PTY_FG_DEFAULT;
136 284 : if (row < 0 || row >= s->rows || col < 0 || col >= s->cols) return PTY_FG_DEFAULT;
137 284 : return s->screen->cells[row * s->cols + col].fg;
138 : }
139 :
140 4512270 : char *pty_row_text(PtySession *s, int row, char *buf, size_t size) {
141 4512270 : if (!s || !s->screen || !buf || size == 0) { if (buf) buf[0] = '\0'; return buf; }
142 4512270 : if (row < 0 || row >= s->rows) { buf[0] = '\0'; return buf; }
143 :
144 4512270 : size_t pos = 0;
145 483600190 : for (int c = 0; c < s->cols && pos + 4 < size; c++) {
146 479087920 : const char *ch = s->screen->cells[row * s->cols + c].ch;
147 479087920 : size_t len = strlen(ch);
148 479087920 : if (pos + len < size) {
149 479087920 : memcpy(buf + pos, ch, len);
150 479087920 : pos += len;
151 : }
152 : }
153 4512270 : buf[pos] = '\0';
154 :
155 : /* Trim trailing spaces */
156 387104281 : while (pos > 0 && buf[pos - 1] == ' ') buf[--pos] = '\0';
157 :
158 4512270 : return buf;
159 : }
160 :
161 4512270 : int pty_row_contains(PtySession *s, int row, const char *text) {
162 : char buf[4096];
163 4512270 : pty_row_text(s, row, buf, sizeof(buf));
164 4512270 : return strstr(buf, text) != NULL;
165 : }
166 :
167 258953 : int pty_screen_contains(PtySession *s, const char *text) {
168 258953 : if (!s || !s->screen) return 0;
169 4643731 : for (int r = 0; r < s->rows; r++) {
170 4487366 : if (pty_row_contains(s, r, text)) return 1;
171 : }
172 156365 : return 0;
173 : }
174 :
175 0 : void pty_get_size(PtySession *s, int *cols, int *rows) {
176 0 : if (cols) *cols = s ? s->cols : 0;
177 0 : if (rows) *rows = s ? s->rows : 0;
178 0 : }
|