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