LCOV - code coverage report
Current view: top level - libs/libptytest - pty_session.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 87.6 % 105 92
Test Date: 2026-04-20 19:54:22 Functions: 84.6 % 13 11

            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              : }
        

Generated by: LCOV version 2.0-1