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 158 : PtySession *pty_open(int cols, int rows) {
29 158 : PtySession *s = calloc(1, sizeof(*s));
30 158 : if (!s) return NULL;
31 158 : s->master_fd = -1;
32 158 : s->child_pid = -1;
33 158 : s->cols = cols;
34 158 : s->rows = rows;
35 158 : s->screen = pty_screen_new(cols, rows);
36 158 : if (!s->screen) { free(s); return NULL; }
37 158 : return s;
38 : }
39 :
40 158 : int pty_run(PtySession *s, const char *argv[]) {
41 158 : struct winsize ws = {
42 158 : .ws_row = (unsigned short)s->rows,
43 158 : .ws_col = (unsigned short)s->cols
44 : };
45 :
46 : int master_fd;
47 158 : pid_t pid = forkpty(&master_fd, NULL, NULL, &ws);
48 158 : if (pid < 0) return -1;
49 :
50 158 : if (pid == 0) {
51 : /* Child process */
52 31 : setenv("TERM", "xterm-256color", 1);
53 31 : setenv("LC_ALL", "en_US.UTF-8", 1);
54 : /* Remove COLUMNS/LINES so the program queries the PTY */
55 31 : unsetenv("COLUMNS");
56 31 : unsetenv("LINES");
57 31 : execvp(argv[0], (char *const *)argv);
58 31 : _exit(127);
59 : }
60 :
61 : /* Parent */
62 127 : s->master_fd = master_fd;
63 127 : s->child_pid = pid;
64 :
65 : /* Set master fd to non-blocking for poll-based reads */
66 127 : int flags = fcntl(master_fd, F_GETFL);
67 127 : if (flags >= 0) fcntl(master_fd, F_SETFL, flags | O_NONBLOCK);
68 :
69 127 : return 0;
70 : }
71 :
72 127 : void pty_close(PtySession *s) {
73 127 : if (!s) return;
74 :
75 127 : if (s->child_pid > 0) {
76 0 : kill(s->child_pid, SIGTERM);
77 : /* Give the child 100ms to exit */
78 0 : usleep(100000);
79 : int status;
80 0 : 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 127 : if (s->master_fd >= 0) close(s->master_fd);
87 127 : pty_screen_free(s->screen);
88 127 : free(s);
89 : }
90 :
91 : /* ── Input ───────────────────────────────────────────────────────────── */
92 :
93 472 : void pty_send(PtySession *s, const char *bytes, size_t len) {
94 472 : if (!s || s->master_fd < 0) return;
95 472 : ssize_t n = write(s->master_fd, bytes, len);
96 : (void)n;
97 : }
98 :
99 256 : void pty_send_key(PtySession *s, PtyKey key) {
100 256 : switch (key) {
101 10 : case PTY_KEY_UP: pty_send(s, "\033[A", 3); break;
102 7 : case PTY_KEY_DOWN: pty_send(s, "\033[B", 3); break;
103 5 : case PTY_KEY_RIGHT: pty_send(s, "\033[C", 3); break;
104 28 : case PTY_KEY_LEFT: pty_send(s, "\033[D", 3); break;
105 5 : case PTY_KEY_PGUP: pty_send(s, "\033[5~", 4); break;
106 5 : case PTY_KEY_PGDN: pty_send(s, "\033[6~", 4); break;
107 7 : case PTY_KEY_HOME: pty_send(s, "\033[H", 3); break;
108 7 : case PTY_KEY_END: pty_send(s, "\033[F", 3); break;
109 182 : default: {
110 182 : char c = (char)key;
111 182 : pty_send(s, &c, 1);
112 : }
113 : }
114 256 : }
115 :
116 119 : void pty_send_str(PtySession *s, const char *str) {
117 119 : if (str) pty_send(s, str, strlen(str));
118 119 : }
119 :
120 : /* ── Screen inspection ───────────────────────────────────────────────── */
121 :
122 0 : const char *pty_cell_text(PtySession *s, int row, int col) {
123 0 : if (!s || !s->screen) return "";
124 0 : if (row < 0 || row >= s->rows || col < 0 || col >= s->cols) return "";
125 0 : return s->screen->cells[row * s->cols + col].ch;
126 : }
127 :
128 0 : int pty_cell_attr(PtySession *s, int row, int col) {
129 0 : if (!s || !s->screen) return 0;
130 0 : if (row < 0 || row >= s->rows || col < 0 || col >= s->cols) return 0;
131 0 : return s->screen->cells[row * s->cols + col].attr;
132 : }
133 :
134 12272 : char *pty_row_text(PtySession *s, int row, char *buf, size_t size) {
135 12272 : if (!s || !s->screen || !buf || size == 0) { if (buf) buf[0] = '\0'; return buf; }
136 12272 : if (row < 0 || row >= s->rows) { buf[0] = '\0'; return buf; }
137 :
138 12272 : size_t pos = 0;
139 1002552 : for (int c = 0; c < s->cols && pos + 4 < size; c++) {
140 990280 : const char *ch = s->screen->cells[row * s->cols + c].ch;
141 990280 : size_t len = strlen(ch);
142 990280 : if (pos + len < size) {
143 990280 : memcpy(buf + pos, ch, len);
144 990280 : pos += len;
145 : }
146 : }
147 12272 : buf[pos] = '\0';
148 :
149 : /* Trim trailing spaces */
150 973971 : while (pos > 0 && buf[pos - 1] == ' ') buf[--pos] = '\0';
151 :
152 12272 : return buf;
153 : }
154 :
155 12272 : int pty_row_contains(PtySession *s, int row, const char *text) {
156 : char buf[4096];
157 12272 : pty_row_text(s, row, buf, sizeof(buf));
158 12272 : return strstr(buf, text) != NULL;
159 : }
160 :
161 834 : int pty_screen_contains(PtySession *s, const char *text) {
162 834 : if (!s || !s->screen) return 0;
163 12709 : for (int r = 0; r < s->rows; r++) {
164 12272 : if (pty_row_contains(s, r, text)) return 1;
165 : }
166 437 : return 0;
167 : }
168 :
169 6 : void pty_get_size(PtySession *s, int *cols, int *rows) {
170 6 : if (cols) *cols = s ? s->cols : 0;
171 6 : if (rows) *rows = s ? s->rows : 0;
172 6 : }
173 :
174 4 : int pty_resize(PtySession *s, int cols, int rows) {
175 4 : if (!s || s->master_fd < 0 || s->child_pid <= 0) return -1;
176 :
177 4 : struct winsize ws = {
178 4 : .ws_row = (unsigned short)rows,
179 4 : .ws_col = (unsigned short)cols
180 : };
181 :
182 4 : if (ioctl(s->master_fd, TIOCSWINSZ, &ws) < 0) return -1;
183 :
184 : /* Deliver SIGWINCH so the child TUI repaints. */
185 4 : if (kill(s->child_pid, SIGWINCH) < 0) return -1;
186 :
187 : /* Resize the virtual screen buffer to match new dimensions. */
188 4 : pty_screen_free(s->screen);
189 4 : s->screen = pty_screen_new(cols, rows);
190 4 : if (!s->screen) return -1;
191 :
192 4 : s->cols = cols;
193 4 : s->rows = rows;
194 :
195 4 : return 0;
196 : }
|