JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
add description of std
[st.git] / std.c
1 /* See LICENSE file for copyright and license details.
2  *
3  * Simple terminal daemon is a terminal emulator. It can be used in
4  * combination with simple terminal to emulate a mostly VT100-compatible
5  * terminal.
6  * 
7  * In this process std works like a filter. It reads data from a
8  * pseudo-terminal and parses the escape sequences and transforms
9  * them into an ed(1)-like. The resulting data is buffered and written
10  * to stdout.
11  * Parallely it reads data from stdin and parses and executes the
12  * commands. The resulting data is written to the pseudo-terminal.
13  */
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <ctype.h>
17 #include <err.h>
18 #include <fcntl.h>
19 #if !(_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600)
20 #include <pty.h>
21 #endif
22 #include <signal.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28
29 #define LENGTH(x)       (sizeof(x) / sizeof((x)[0]))
30 #define MAX(a,b)        (((a) > (b)) ? (a) : (b))
31 #define MIN(a,b)        (((a) < (b)) ? (a) : (b))
32
33 typedef struct {
34         unsigned char data[BUFSIZ];
35         int s, e;
36         int n;
37 } RingBuffer;
38
39 typedef struct {
40         unsigned char data[BUFSIZ];
41         int i, n;
42         int fd;
43 } ReadBuffer;
44
45 static void buffer(char c);
46 static void cmd(const char *cmdstr, ...);
47 static int getch(ReadBuffer *buf);
48 static void getpty(void);
49 static void movea(int x, int y);
50 static void mover(int x, int y);
51 static void parsecmd(void);
52 static void parseesc(void);
53 static void scroll(int l);
54 static void shell(void);
55 static void sigchld(int n);
56 static char unbuffer(void);
57 static void ungetch(ReadBuffer *buf, int c);
58
59 static int cols = 80, lines = 25;
60 static int cx = 0, cy = 0;
61 static int c;
62 static int ptm, pts;
63 static _Bool bold, digit, qmark;
64 static pid_t pid;
65 static RingBuffer buf;
66 static ReadBuffer cmdbuf, ptmbuf;
67
68 void
69 buffer(char c) {
70         if(buf.n < LENGTH(buf.data))
71                 buf.n++;
72         else
73                 buf.s = (buf.s + 1) % LENGTH(buf.data);
74         buf.data[buf.e++] = c;
75         buf.e %= LENGTH(buf.data);
76 }
77
78 void
79 cmd(const char *cmdstr, ...) {
80         va_list ap;
81
82         putchar('\n');
83         putchar(':');
84         va_start(ap, cmdstr);
85         vfprintf(stdout, cmdstr, ap);
86         va_end(ap);
87 }
88
89 int
90 getch(ReadBuffer *buf) {
91         if(buf->i++ >= buf->n) {
92                 buf->n = read(buf->fd, buf->data, BUFSIZ);
93                 if(buf->n == -1)
94                         err(EXIT_FAILURE, "cannot read");
95                 buf->i = 0;
96         }
97         return buf->data[buf->i];
98 }
99
100 void
101 movea(int x, int y) {
102         x = MAX(x, cols);
103         y = MAX(y, lines);
104         cx = x;
105         cy = y;
106         cmd("seek(%d,%d)", x, y);
107 }
108
109 void
110 mover(int x, int y) {
111         movea(cx + x, cy + y);
112 }
113
114 void
115 parsecmd(void) {
116 }
117
118 void
119 parseesc(void) {
120         int i, j;
121         int arg[16];
122
123         memset(arg, 0, LENGTH(arg));
124         c = getch(&ptmbuf);
125         switch(c) {
126         case '[':
127                 c = getch(&ptmbuf);
128                 for(j = 0; j < LENGTH(arg);) {
129                         if(isdigit(c)) {
130                                 digit = 1;
131                                 arg[j] *= 10;
132                                 arg[j] += c - '0';
133                         }
134                         else if(c == '?')
135                                 qmark = 1;
136                         else if(c == ';') {
137                                 if(!digit)
138                                         errx(EXIT_FAILURE, "syntax error");
139                                 digit = 0;
140                                 j++;
141                         }
142                         else {
143                                 if(digit) {
144                                         digit = 0;
145                                         j++;
146                                 }
147                                 break;
148                         }
149                         c = getch(&ptmbuf);
150                 }
151                 switch(c) {
152                 case '@':
153                         break;
154                 case 'A':
155                         mover(0, j ? arg[0] : 1);
156                         break;
157                 case 'B':
158                         mover(0, j ? -arg[0] : -1);
159                         break;
160                 case 'C':
161                         mover(j ? arg[0] : 1, 0);
162                         break;
163                 case 'D':
164                         mover(j ? -arg[0] : -1, 0);
165                         break;
166                 case 'E':
167                         /* movel(j ? arg[0] : 1); */
168                         break;
169                 case 'F':
170                         /* movel(j ? -arg[0] : -1); */
171                         break;
172                 case '`':
173                 case 'G':
174                         movea(j ? arg[0] : 1, cy);
175                         break;
176                 case 'f':
177                 case 'H':
178                         movea(arg[1] ? arg[1] : 1, arg[0] ? arg[0] : 1);
179                 case 'L':
180                         /* insline(j ? arg[0] : 1); */
181                         break;
182                 case 'M':
183                         /* delline(j ? arg[0] : 1); */
184                         break;
185                 case 'P':
186                         break;
187                 case 'S':
188                         scroll(j ? arg[0] : 1);
189                         break;
190                 case 'T':
191                         scroll(j ? -arg[0] : -1);
192                         break;
193                 case 'd':
194                         movea(cx, j ? arg[0] : 1);
195                         break;
196                 case 'm':
197                         for(i = 0; i < j; i++) {
198                                 if(arg[i] >= 30 && arg[i] <= 37)
199                                         cmd("#%d", arg[i] - 30);
200                                 if(arg[i] >= 40 && arg[i] <= 47)
201                                         cmd("|%d", arg[i] - 40);
202                                 /* xterm bright colors */
203                                 if(arg[i] >= 90 && arg[i] <= 97)
204                                         cmd("#%d", arg[i] - 90);
205                                 if(arg[i] >= 100 && arg[i] <= 107)
206                                         cmd("|%d", arg[i] - 100);
207                                 switch(arg[i]) {
208                                 case 0:
209                                 case 22:
210                                         if(bold)
211                                                 cmd("bold");
212                                 case 1:
213                                         if(!bold)
214                                                 cmd("bold");
215                                         break;
216                                 }
217                         }
218                         break;
219                 }
220                 break;
221         default:
222                 putchar('\033');
223                 ungetch(&ptmbuf, c);
224         }
225 }
226
227 void
228 scroll(int l) {
229         cmd("seek(%d,%d)", cx, cy + l);
230 }
231
232 void
233 getpty(void) {
234         char *ptsdev;
235
236 #if defined(_GNU_SOURCE)
237         ptm = getpt();
238 #elif _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
239         ptm = posix_openpt(O_RDWR);
240 #else
241         ptm = open("/dev/ptmx", O_RDWR);
242         if(ptm == -1)
243                 if(openpty(&ptm, &pts, NULL, NULL, NULL) == -1)
244                         err(EXIT_FAILURE, "cannot open pty");
245 #endif
246 #if defined(_XOPEN_SOURCE)
247         if(ptm != -1) {
248                 if(grantpt(ptm) == -1)
249                         err(EXIT_FAILURE, "cannot grant access to pty");
250                 if(unlockpt(ptm) == -1)
251                         err(EXIT_FAILURE, "cannot unlock pty");
252                 ptsdev = ptsname(ptm);
253                 if(!ptsdev)
254                         err(EXIT_FAILURE, "slave pty name undefined");
255                 pts = open(ptsdev, O_RDWR);
256                 if(pts == -1)
257                         err(EXIT_FAILURE, "cannot open slave pty");
258         }
259         else
260                 err(EXIT_FAILURE, "cannot open pty");
261 #endif
262 }
263
264 void
265 shell(void) {
266         static char *shell = NULL;
267
268         if(!shell && !(shell = getenv("SHELL")))
269                 shell = "/bin/sh";
270         pid = fork();
271         switch(pid) {
272         case -1:
273                 err(EXIT_FAILURE, "cannot fork");
274         case 0:
275                 setsid();
276                 dup2(pts, STDIN_FILENO);
277                 dup2(pts, STDOUT_FILENO);
278                 dup2(pts, STDERR_FILENO);
279                 close(ptm);
280                 putenv("TERM=vt102");
281                 execvp(shell, NULL);
282                 break;
283         default:
284                 close(pts);
285                 signal(SIGCHLD, sigchld);
286         }
287 }
288
289 void
290 sigchld(int n) {
291         int ret;
292
293         if(waitpid(pid, &ret, 0) == -1)
294                 err(EXIT_FAILURE, "waiting for child failed");
295         if(WIFEXITED(ret))
296                 exit(WEXITSTATUS(ret));
297         else
298                 exit(EXIT_SUCCESS);
299 }
300
301 char
302 unbuffer(void) {
303         char c;
304
305         c = buf.data[buf.s++];
306         buf.s %= LENGTH(buf.data);
307         buf.n--;
308         return c;
309 }
310
311 void
312 ungetch(ReadBuffer *buf, int c) {
313         if(buf->i + 1 >= buf->n)
314                 errx(EXIT_FAILURE, "buffer full");
315         buf->data[buf->i++] = c;
316 }
317
318 int
319 main(int argc, char *argv[]) {
320         fd_set rfds;
321
322         if(argc == 2 && !strcmp("-v", argv[1]))
323                 errx(EXIT_SUCCESS, "std-"VERSION", © 2008 Matthias-Christian Ott");
324         else if(argc == 1)
325                 errx(EXIT_FAILURE, "usage: std [-v]");
326         getpty();
327         shell();
328         cmdbuf.fd = STDIN_FILENO;
329         ptmbuf.fd = ptm;
330         FD_ZERO(&rfds);
331         FD_SET(STDIN_FILENO, &rfds);
332         FD_SET(ptm, &rfds);
333         for(;;) {
334                 if(select(ptm + 1, &rfds, NULL, NULL, NULL) == -1)
335                         err(EXIT_FAILURE, "cannot select");
336                 if(FD_ISSET(STDIN_FILENO, &rfds))
337                         do {
338                                 c = getch(&cmdbuf);
339                                 switch(c) {
340                                 case ':':
341                                         parsecmd();
342                                         break;
343                                 default:
344                                         break;
345                                 }
346                         } while(cmdbuf.i < cmdbuf.n);
347                 if(FD_ISSET(ptm, &rfds)) {
348                         do {
349                                 c = getch(&ptmbuf);
350                                 switch(c) {
351                                 case '\033':
352                                         parseesc();
353                                         break;
354                                 default:
355                                         putchar(c);
356                                 }
357                         } while(ptmbuf.i < ptmbuf.n);
358                         fflush(stdout);
359                 }
360         }
361         return 0;
362 }