C · 4659 bytes Raw Blame History
1 /*
2 * PTY helper functions for fortty
3 * Wraps POSIX PTY operations for Fortran binding
4 */
5
6 #define _XOPEN_SOURCE 600
7
8 /* Platform-specific PTY header */
9 #if defined(__APPLE__)
10 #include <util.h>
11 #else
12 #include <pty.h>
13 #endif
14
15 #include <unistd.h>
16 #include <fcntl.h>
17 #include <sys/ioctl.h>
18 #include <sys/wait.h>
19 #include <signal.h>
20 #include <errno.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <stdio.h>
24
25 /* Store child PID for status checking */
26 static pid_t child_pid = -1;
27
28 /*
29 * Fork a shell process with PTY
30 * Returns master fd on success, -1 on error
31 */
32 int fortty_pty_fork(const char *shell, int rows, int cols) {
33 int master_fd;
34 struct winsize ws;
35
36 /* Set initial window size */
37 ws.ws_row = rows;
38 ws.ws_col = cols;
39 ws.ws_xpixel = 0;
40 ws.ws_ypixel = 0;
41
42 /* forkpty does: openpty + fork + login_tty */
43 child_pid = forkpty(&master_fd, NULL, NULL, &ws);
44
45 if (child_pid < 0) {
46 perror("forkpty");
47 return -1;
48 }
49
50 if (child_pid == 0) {
51 /* Child process */
52
53 /* Set environment for terminal */
54 setenv("TERM", "xterm-256color", 1);
55 setenv("COLORTERM", "truecolor", 1);
56
57 /* Determine shell to use */
58 const char *sh = shell;
59 if (!sh || sh[0] == '\0') {
60 sh = getenv("SHELL");
61 if (!sh) {
62 sh = "/bin/sh";
63 }
64 }
65
66 /* Execute shell as interactive shell (not login) */
67 execlp(sh, sh, (char *)NULL);
68
69 /* If exec fails */
70 perror("execlp");
71 _exit(127);
72 }
73
74 /* Parent process */
75
76 /* Set non-blocking mode on master */
77 int flags = fcntl(master_fd, F_GETFL, 0);
78 if (flags != -1) {
79 fcntl(master_fd, F_SETFL, flags | O_NONBLOCK);
80 }
81
82 return master_fd;
83 }
84
85 /*
86 * Set PTY window size
87 * Returns 0 on success, -1 on error
88 */
89 int fortty_pty_set_size(int master_fd, int rows, int cols) {
90 struct winsize ws;
91 ws.ws_row = rows;
92 ws.ws_col = cols;
93 ws.ws_xpixel = 0;
94 ws.ws_ypixel = 0;
95
96 if (ioctl(master_fd, TIOCSWINSZ, &ws) < 0) {
97 perror("ioctl TIOCSWINSZ");
98 return -1;
99 }
100
101 return 0;
102 }
103
104 /*
105 * Read from PTY (non-blocking)
106 * Returns: bytes read (>0), 0 if would block, -1 on error/EOF
107 */
108 int fortty_pty_read(int fd, char *buf, int count) {
109 ssize_t n = read(fd, buf, count);
110
111 if (n < 0) {
112 if (errno == EAGAIN || errno == EWOULDBLOCK) {
113 return 0; /* No data available */
114 }
115 return -1; /* Actual error */
116 }
117
118 if (n == 0) {
119 return -1; /* EOF - child closed PTY */
120 }
121
122 return (int)n;
123 }
124
125 /*
126 * Write to PTY
127 * Returns: bytes written, -1 on error
128 */
129 int fortty_pty_write(int fd, const char *buf, int count) {
130 ssize_t n = write(fd, buf, count);
131 if (n < 0) {
132 if (errno == EAGAIN || errno == EWOULDBLOCK) {
133 return 0; /* Would block, try again later */
134 }
135 perror("write to pty");
136 return -1;
137 }
138 return (int)n;
139 }
140
141 /*
142 * Close PTY and wait for child
143 */
144 void fortty_pty_close(int master_fd) {
145 if (master_fd >= 0) {
146 close(master_fd);
147 }
148
149 if (child_pid > 0) {
150 /* Send SIGHUP to child (standard terminal close behavior) */
151 kill(child_pid, SIGHUP);
152
153 /* Wait for child to exit (with timeout via WNOHANG loop) */
154 int status;
155 int attempts = 0;
156 while (waitpid(child_pid, &status, WNOHANG) == 0 && attempts < 100) {
157 usleep(10000); /* 10ms */
158 attempts++;
159 }
160
161 /* Force kill if still running */
162 if (attempts >= 100) {
163 kill(child_pid, SIGKILL);
164 waitpid(child_pid, &status, 0);
165 }
166
167 child_pid = -1;
168 }
169 }
170
171 /*
172 * Check if child process is still alive
173 * Returns: 1 if alive, 0 if dead/not started
174 */
175 int fortty_pty_child_alive(void) {
176 if (child_pid <= 0) {
177 return 0;
178 }
179
180 int status;
181 pid_t result = waitpid(child_pid, &status, WNOHANG);
182
183 if (result == 0) {
184 /* Child still running */
185 return 1;
186 }
187
188 if (result == child_pid) {
189 /* Child exited */
190 child_pid = -1;
191 return 0;
192 }
193
194 /* Error (treat as dead) */
195 return 0;
196 }
197
198 /*
199 * Get the child PID (for debugging/signals)
200 */
201 int fortty_pty_get_child_pid(void) {
202 return (int)child_pid;
203 }
204
205 /*
206 * Window blur stub - macOS blur requires Cocoa framework which has
207 * CMake integration issues with Fortran. This is a no-op placeholder.
208 * TODO: Implement with NSVisualEffectView when build system supports it.
209 */
210 void fortty_set_window_blur(void* window, int enable) {
211 (void)window;
212 (void)enable;
213 }