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 21058 : PtySession *pty_open(int cols, int rows) {
29 21058 : PtySession *s = calloc(1, sizeof(*s));
30 21058 : if (!s) return NULL;
31 21058 : s->master_fd = -1;
32 21058 : s->child_pid = -1;
33 21058 : s->cols = cols;
34 21058 : s->rows = rows;
35 21058 : s->screen = pty_screen_new(cols, rows);
36 21058 : if (!s->screen) { free(s); return NULL; }
37 21058 : return s;
38 : }
39 :
40 21058 : int pty_run(PtySession *s, const char *argv[]) {
41 21058 : struct winsize ws = {
42 21058 : .ws_row = (unsigned short)s->rows,
43 21058 : .ws_col = (unsigned short)s->cols
44 : };
45 :
46 : int master_fd;
47 21058 : pid_t pid = forkpty(&master_fd, NULL, NULL, &ws);
48 21058 : if (pid < 0) return -1;
49 :
50 21058 : if (pid == 0) {
51 : /* Child process */
52 222 : setenv("TERM", "xterm-256color", 1);
53 222 : setenv("LC_ALL", "en_US.UTF-8", 1);
54 : /* Remove COLUMNS/LINES so the program queries the PTY */
55 222 : unsetenv("COLUMNS");
56 222 : unsetenv("LINES");
57 222 : execvp(argv[0], (char *const *)argv);
58 222 : _exit(127);
59 : }
60 :
61 : /* Parent */
62 20836 : s->master_fd = master_fd;
63 20836 : s->child_pid = pid;
64 :
65 : /* Set master fd to non-blocking for poll-based reads */
66 20836 : int flags = fcntl(master_fd, F_GETFL);
67 20836 : if (flags >= 0) fcntl(master_fd, F_SETFL, flags | O_NONBLOCK);
68 :
69 20836 : return 0;
70 : }
71 :
72 20836 : void pty_close(PtySession *s) {
73 20836 : if (!s) return;
74 :
75 20836 : if (s->child_pid > 0) {
76 20836 : kill(s->child_pid, SIGTERM);
77 : /* Give the child 100ms to exit */
78 20836 : usleep(100000);
79 : int status;
80 20836 : 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 20836 : if (s->master_fd >= 0) close(s->master_fd);
87 20836 : pty_screen_free(s->screen);
88 20836 : free(s);
89 : }
90 :
91 : /* ── Input ───────────────────────────────────────────────────────────── */
92 :
93 101079 : void pty_send(PtySession *s, const char *bytes, size_t len) {
94 101079 : if (!s || s->master_fd < 0) return;
95 101079 : ssize_t n = write(s->master_fd, bytes, len);
96 : (void)n;
97 : }
98 :
99 92028 : void pty_send_key(PtySession *s, PtyKey key) {
100 92028 : switch (key) {
101 727 : case PTY_KEY_UP: pty_send(s, "\033[A", 3); break;
102 44095 : case PTY_KEY_DOWN: pty_send(s, "\033[B", 3); break;
103 143 : case PTY_KEY_RIGHT: pty_send(s, "\033[C", 3); break;
104 143 : 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 7835 : case PTY_KEY_HOME: pty_send(s, "\033[H", 3); break;
108 443 : case PTY_KEY_END: pty_send(s, "\033[F", 3); break;
109 37499 : default: {
110 37499 : char c = (char)key;
111 37499 : pty_send(s, &c, 1);
112 : }
113 : }
114 92028 : }
115 :
116 9051 : void pty_send_str(PtySession *s, const char *str) {
117 9051 : if (str) pty_send(s, str, strlen(str));
118 9051 : }
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 499 : int pty_cell_attr(PtySession *s, int row, int col) {
129 499 : if (!s || !s->screen) return 0;
130 499 : if (row < 0 || row >= s->rows || col < 0 || col >= s->cols) return 0;
131 499 : return s->screen->cells[row * s->cols + col].attr;
132 : }
133 :
134 158 : int pty_cell_fg(PtySession *s, int row, int col) {
135 158 : if (!s || !s->screen) return PTY_FG_DEFAULT;
136 158 : if (row < 0 || row >= s->rows || col < 0 || col >= s->cols) return PTY_FG_DEFAULT;
137 158 : return s->screen->cells[row * s->cols + col].fg;
138 : }
139 :
140 3311163 : char *pty_row_text(PtySession *s, int row, char *buf, size_t size) {
141 3311163 : if (!s || !s->screen || !buf || size == 0) { if (buf) buf[0] = '\0'; return buf; }
142 3311163 : if (row < 0 || row >= s->rows) { buf[0] = '\0'; return buf; }
143 :
144 3311163 : size_t pos = 0;
145 337891323 : for (int c = 0; c < s->cols && pos + 4 < size; c++) {
146 334580160 : const char *ch = s->screen->cells[row * s->cols + c].ch;
147 334580160 : size_t len = strlen(ch);
148 334580160 : if (pos + len < size) {
149 334580160 : memcpy(buf + pos, ch, len);
150 334580160 : pos += len;
151 : }
152 : }
153 3311163 : buf[pos] = '\0';
154 :
155 : /* Trim trailing spaces */
156 266343272 : while (pos > 0 && buf[pos - 1] == ' ') buf[--pos] = '\0';
157 :
158 3311163 : return buf;
159 : }
160 :
161 3311163 : int pty_row_contains(PtySession *s, int row, const char *text) {
162 : char buf[4096];
163 3311163 : pty_row_text(s, row, buf, sizeof(buf));
164 3311163 : return strstr(buf, text) != NULL;
165 : }
166 :
167 200269 : int pty_screen_contains(PtySession *s, const char *text) {
168 200269 : if (!s || !s->screen) return 0;
169 3408139 : for (int r = 0; r < s->rows; r++) {
170 3291168 : if (pty_row_contains(s, r, text)) return 1;
171 : }
172 116971 : 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 : }
|