JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
drawing is faster but the bold attr is not supported anymore.
[st.git] / st.c
1 /* See LICENSE for licence details. */
2 #define _XOPEN_SOURCE 600
3 #include <ctype.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <limits.h>
7 #include <locale.h>
8 #include <stdarg.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <signal.h>
13 #include <sys/ioctl.h>
14 #include <sys/select.h>
15 #include <sys/stat.h>
16 #include <sys/types.h>
17 #include <sys/wait.h>
18 #include <unistd.h>
19 #include <X11/Xlib.h>
20 #include <X11/keysym.h>
21 #include <X11/Xutil.h>
22
23 #define TNAME "st"
24
25 /* Arbitrary sizes */
26 #define ESCSIZ 256
27 #define ESCARG 16
28 #define MAXDRAWBUF 1024
29
30 #define SERRNO strerror(errno)
31 #define MIN(a, b)  ((a) < (b) ? (a) : (b))
32 #define MAX(a, b)  ((a) < (b) ? (b) : (a))
33 #define LEN(a)     (sizeof(a) / sizeof(a[0]))
34 #define DEFAULT(a, b)     (a) = (a) ? (a) : (b)    
35 #define BETWEEN(x, a, b)  ((a) <= (x) && (x) <= (b))
36 #define LIMIT(x, a, b)    (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
37 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
38
39 /* Attribute, Cursor, Character state, Terminal mode, Screen draw mode */
40 enum { ATnone=0 , ATreverse=1 , ATunderline=2, ATbold=4 };
41 enum { CSup, CSdown, CSright, CSleft, CShide, CSdraw, CSwrap, CSsave, CSload };
42 enum { CRset=1, CRupdate=2 };
43 enum { TMwrap=1, TMinsert=2 };
44 enum { SCupdate, SCredraw };
45
46 typedef int Color;
47
48 typedef struct {
49         char c;     /* character code  */
50         char mode;  /* attribute flags */
51         Color fg;   /* foreground      */
52         Color bg;   /* background      */
53         char state; /* state flag      */
54 } Glyph;
55
56 typedef Glyph* Line;
57
58 typedef struct {
59         Glyph attr;  /* current char attributes */
60         char hidden;
61         int x;
62         int y;
63 } TCursor;
64
65 /* Escape sequence structs */
66 /* ESC <pre> [[ [<priv>] <arg> [;]] <mode>] */
67 typedef struct {
68         char buf[ESCSIZ+1]; /* raw string */
69         int len;            /* raw string length */
70         char pre;           
71         char priv;
72         int arg[ESCARG+1];
73         int narg;           /* nb of args */
74         char mode;
75 } Escseq;
76
77 /* Internal representation of the screen */
78 typedef struct {
79         int row;    /* nb row */  
80         int col;    /* nb col */
81         Line* line; /* screen */
82         TCursor c;  /* cursor */
83         int top;    /* top    scroll limit */
84         int bot;    /* bottom scroll limit */
85         int mode;   /* terminal mode */
86 } Term;
87
88 /* Purely graphic info */
89 typedef struct {
90         Display* dis;
91         Window win;
92         int scr;
93         int w;  /* window width  */
94         int h;  /* window height */
95         int ch; /* char height */
96         int cw; /* char width  */
97 } XWindow; 
98
99 typedef struct {
100         KeySym k;
101         char s[ESCSIZ];
102 } Key;
103
104 #include "config.h"
105
106 /* Drawing Context */
107 typedef struct {
108         unsigned long col[LEN(colorname)];
109         XFontStruct* font;
110         GC gc;
111 } DC;
112
113 static void die(const char *errstr, ...);
114 static void draw(int);
115 static void execsh(void);
116 static void sigchld(int);
117 static void run(void);
118
119 static int escaddc(char);
120 static int escfinal(char);
121 static void escdump(void);
122 static void eschandle(void);
123 static void escparse(void);
124 static void escreset(void);
125
126 static void tclearregion(int, int, int, int);
127 static void tcpos(int);
128 static void tcursor(int);
129 static void tdeletechar(int);
130 static void tdeleteline(int);
131 static void tinsertblank(int);
132 static void tinsertblankline(int);
133 static void tmoveto(int, int);
134 static void tnew(int, int);
135 static void tnewline(void);
136 static void tputc(char);
137 static void tputs(char*, int);
138 static void tresize(int, int);
139 static void tscroll(void);
140 static void tsetattr(int*, int);
141 static void tsetchar(char);
142 static void tsetscroll(int, int);
143
144 static void ttynew(void);
145 static void ttyread(void);
146 static void ttyresize(int, int);
147 static void ttywrite(const char *, size_t);
148
149 static unsigned long xgetcol(const char *);
150 static void xclear(int, int, int, int);
151 static void xcursor(int);
152 static void xdrawc(int, int, Glyph);
153 static void xinit(void);
154 static void xscroll(void);
155
156 static void expose(XEvent *);
157 static char * kmap(KeySym);
158 static void kpress(XEvent *);
159 static void resize(XEvent *);
160
161 static void (*handler[LASTEvent])(XEvent *) = {
162         [KeyPress] = kpress,
163         [Expose] = expose,
164         [ConfigureNotify] = resize
165 };
166
167 /* Globals */
168 static DC dc;
169 static XWindow xw;
170 static Term term;
171 static Escseq escseq;
172 static int cmdfd;
173 static pid_t pid;
174 static int running;
175
176 #ifdef DEBUG
177 void
178 tdump(void) {
179         int row, col;
180         Glyph c;
181
182         for(row = 0; row < term.row; row++) {
183                 for(col = 0; col < term.col; col++) {
184                         if(col == term.c.x && row == term.c.y)
185                                 putchar('#');
186                         else {
187                                 c = term.line[row][col];
188                                 putchar(c.state & CRset ? c.c : '.');
189                         }
190                 }
191                 putchar('\n');
192         }
193 }
194 #endif
195
196 void
197 die(const char *errstr, ...) {
198         va_list ap;
199
200         va_start(ap, errstr);
201         vfprintf(stderr, errstr, ap);
202         va_end(ap);
203         exit(EXIT_FAILURE);
204 }
205
206 void
207 execsh(void) {
208         char *args[3] = {SHELL, "-i", NULL};
209         putenv("TERM=" TNAME);
210         execvp(SHELL, args);
211 }
212
213 void
214 xbell(void) { /* visual bell */
215         XRectangle r = { 0, 0, xw.w, xw.h };
216         XSetForeground(xw.dis, dc.gc, dc.col[BellCol]);
217         XFillRectangles(xw.dis, xw.win, dc.gc, &r, 1);
218         /* usleep(30000); */
219         draw(SCredraw);
220 }
221
222 void 
223 sigchld(int a) {
224         int stat = 0;
225         if(waitpid(pid, &stat, 0) < 0)
226                 die("Waiting for pid %hd failed: %s\n", pid, SERRNO);
227         if(WIFEXITED(stat))
228                 exit(WEXITSTATUS(stat));
229         else
230                 exit(EXIT_FAILURE);
231 }
232
233 void
234 ttynew(void) {
235         int m, s;
236         char *pts;
237
238         if((m = posix_openpt(O_RDWR | O_NOCTTY)) < 0)
239                 die("openpt failed: %s\n", SERRNO);
240         if(grantpt(m) < 0)
241                 die("grandpt failed: %s\n", SERRNO);
242         if(unlockpt(m) < 0)
243                 die("unlockpt failed: %s\n", SERRNO);
244         if(!(pts = ptsname(m)))
245                 die("ptsname failed: %s\n", SERRNO);
246         if((s = open(pts, O_RDWR | O_NOCTTY)) < 0)
247                 die("Couldn't open slave: %s\n", SERRNO);
248         fcntl(s, F_SETFL, O_NDELAY);
249         switch(pid = fork()) {
250         case -1:
251                 die("fork failed\n");
252                 break;
253         case 0:
254                 setsid(); /* create a new process group */
255                 dup2(s, STDIN_FILENO);
256                 dup2(s, STDOUT_FILENO);
257                 dup2(s, STDERR_FILENO);
258                 if(ioctl(s, TIOCSCTTY, NULL) < 0)
259                         die("ioctl TTIOCSTTY failed: %s\n", SERRNO);
260                 execsh();
261                 break;
262         default:
263                 close(s);
264                 cmdfd = m;
265                 signal(SIGCHLD, sigchld);
266         }
267 }
268
269 void
270 dump(char c) {
271         static int col;
272         fprintf(stderr, " %02x %c ", c, isprint(c)?c:'.');
273         if(++col % 10 == 0)
274                 fprintf(stderr, "\n");
275 }
276
277 void
278 ttyread(void) {
279         char buf[BUFSIZ] = {0};
280         int ret;
281
282         switch(ret = read(cmdfd, buf, BUFSIZ)) {
283         case -1: 
284                 die("Couldn't read from shell: %s\n", SERRNO);
285                 break;
286         default:
287                 tputs(buf, ret);
288         }
289 }
290
291 void
292 ttywrite(const char *s, size_t n) {
293         if(write(cmdfd, s, n) == -1)
294                 die("write error on tty: %s\n", SERRNO);
295 }
296
297 void
298 ttyresize(int x, int y) {
299         struct winsize w;
300
301         w.ws_row = term.row;
302         w.ws_col = term.col;
303         w.ws_xpixel = w.ws_ypixel = 0;
304         if(ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
305                 fprintf(stderr, "Couldn't set window size: %s\n", SERRNO);
306 }
307
308 int
309 escfinal(char c) {
310         if(escseq.len == 1)
311                 switch(c) {
312                 case '[':
313                 case ']':
314                 case '(':
315                         return 0;
316                 case '=':
317                 case '>':
318                 default:
319                         return 1;
320                 }
321         else if(BETWEEN(c, 0x40, 0x7E))
322                 return 1;
323         return 0;         
324 }
325
326 void
327 tcpos(int mode) {
328         static int x = 0;
329         static int y = 0;
330
331         if(mode == CSsave)
332                 x = term.c.x, y = term.c.y;
333         else if(mode == CSload)
334                 tmoveto(x, y);
335 }
336
337 void
338 tnew(int col, int row) {   /* screen size */
339         term.row = row, term.col = col;
340         term.top = 0, term.bot = term.row - 1;
341         /* mode */
342         term.mode = TMwrap;
343         /* cursor */
344         term.c.attr.mode = ATnone;
345         term.c.attr.fg = DefaultFG;
346         term.c.attr.bg = DefaultBG;
347         term.c.x = term.c.y = 0;
348         term.c.hidden = 0;
349         /* allocate screen */
350         term.line = calloc(term.row, sizeof(Line));
351         for(row = 0 ; row < term.row; row++)
352                 term.line[row] = calloc(term.col, sizeof(Glyph));
353 }
354
355 void
356 tscroll(void) {
357         Line temp = term.line[term.top];
358         int i;
359         /* X stuff _before_ the line swapping (results in wrong line index) */
360         xscroll();
361         for(i = term.top; i < term.bot; i++)
362                 term.line[i] = term.line[i+1];
363         memset(temp, 0, sizeof(Glyph) * term.col);
364         term.line[term.bot] = temp;
365 }
366
367 void
368 tnewline(void) {
369         int y = term.c.y + 1;
370         if(y > term.bot)
371                 tscroll(), y = term.bot;
372         tmoveto(0, y);
373 }
374
375 int
376 escaddc(char c) {
377         escseq.buf[escseq.len++] = c;
378         if(escfinal(c) || escseq.len >= ESCSIZ) {
379                 escparse(), eschandle();
380                 return 0;
381         }
382         return 1;
383 }
384
385 void
386 escparse(void) {
387         /* int noarg = 1; */
388         char *p = escseq.buf;
389
390         escseq.narg = 0;
391         switch(escseq.pre = *p++) {
392         case '[': /* CSI */
393                 if(*p == '?')
394                         escseq.priv = 1, p++;
395
396                 while(p < escseq.buf+escseq.len) {
397                         while(isdigit(*p)) {
398                                 escseq.arg[escseq.narg] *= 10;
399                                 escseq.arg[escseq.narg] += *(p++) - '0'/*, noarg = 0 */;
400                         }
401                         if(*p == ';')
402                                 escseq.narg++, p++;
403                         else {
404                                 escseq.mode = *p;
405                                 escseq.narg++;
406                                 return;
407                         }
408                 }
409                 break;
410         case '(':
411                 /* XXX: graphic character set */
412                 break;
413         }
414 }
415
416 void
417 tmoveto(int x, int y) {
418         term.c.x = x < 0 ? 0 : x >= term.col ? term.col-1 : x;
419         term.c.y = y < 0 ? 0 : y >= term.row ? term.row-1 : y;
420 }
421
422 void
423 tcursor(int dir) {
424         int xf = term.c.x, yf = term.c.y;
425
426         switch(dir) {
427         case CSup:
428                 yf--;
429                 break;
430         case CSdown:
431                 yf++;
432                 break;
433         case CSleft:
434                 xf--;
435                 if(xf < 0) {
436                         xf = term.col-1, yf--;
437                         if(yf < term.top)
438                                 yf = term.top, xf = 0;
439                 }
440                 break;
441         case CSright:
442                 xf++;
443                 if(xf >= term.col) {
444                         xf = 0, yf++;
445                         if(yf > term.bot)
446                                 yf = term.bot, tscroll();
447                 }
448                 break;
449         }
450         tmoveto(xf, yf);
451 }
452
453 void
454 tsetchar(char c) {
455         term.line[term.c.y][term.c.x] = term.c.attr;
456         term.line[term.c.y][term.c.x].c = c;
457         term.line[term.c.y][term.c.x].state |= CRset | CRupdate;
458 }
459
460 void
461 tclearregion(int x1, int y1, int x2, int y2) {
462         int x, y;
463
464         LIMIT(x1, 0, term.col-1);
465         LIMIT(x2, 0, term.col-1);
466         LIMIT(y1, 0, term.row-1);
467         LIMIT(y2, 0, term.row-1);
468
469         /* XXX: could be optimized */
470         for(x = x1; x <= x2; x++)
471                 for(y = y1; y <= y2; y++)
472                         memset(&term.line[y][x], 0, sizeof(Glyph));
473
474         xclear(x1, y1, x2, y2);
475 }
476
477 void
478 tdeletechar(int n) {
479         int src = term.c.x + n;
480         int dst = term.c.x;
481         int size = term.col - src;
482
483         if(src >= term.col) {
484                 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
485                 return;
486         }
487         memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph));
488         tclearregion(term.col-size, term.c.y, term.col-1, term.c.y);
489 }
490
491 void
492 tinsertblank(int n) {
493         int src = term.c.x;
494         int dst = src + n;
495         int size = term.col - n - src;
496
497         if(dst >= term.col) {
498                 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
499                 return;
500         }
501         memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph));
502         tclearregion(src, term.c.y, dst, term.c.y);
503 }
504
505 void
506 tsetlinestate(int n, int state) {
507         int i;
508         for(i = 0; i < term.col; i++)
509                 term.line[n][i].state |= state;
510 }
511
512 void
513 tinsertblankline (int n) {
514         int i;
515         Line blank;
516         int bot = term.bot;
517
518         if(term.c.y > term.bot)
519                 bot = term.row - 1;
520         else if(term.c.y < term.top)
521                 bot = term.top - 1;
522         if(term.c.y + n >= bot) {
523                 tclearregion(0, term.c.y, term.col-1, bot);
524                 return;
525         }
526         for(i = bot; i >= term.c.y+n; i--) {
527                 /* swap deleted line <-> blanked line */
528                 blank = term.line[i];
529                 term.line[i] = term.line[i-n];
530                 term.line[i-n] = blank;
531                 /* blank it */
532                 memset(blank, 0, term.col * sizeof(Glyph));
533                 tsetlinestate(i, CRupdate);
534                 tsetlinestate(i-n, CRupdate);
535         }
536 }
537
538 void
539 tdeleteline(int n) {
540         int i;
541         Line blank;
542         int bot = term.bot;
543
544         if(term.c.y > term.bot)
545                 bot = term.row - 1;
546         else if(term.c.y < term.top)
547                 bot = term.top - 1;
548         if(term.c.y + n >= bot) {
549                 tclearregion(0, term.c.y, term.col-1, bot);
550                 return;
551         }
552         for(i = term.c.y; i <= bot-n; i++) {
553                 /* swap deleted line <-> blanked line */
554                 blank = term.line[i];
555                 term.line[i] = term.line[i+n];
556                 term.line[i+n] = blank;
557                 /* blank it */
558                 memset(blank, 0, term.col * sizeof(Glyph));
559                 tsetlinestate(i, CRupdate);
560                 tsetlinestate(i-n, CRupdate);
561         }
562 }
563
564 void
565 tsetattr(int *attr, int l) {
566         int i;
567
568         for(i = 0; i < l; i++) {
569                 switch(attr[i]) {
570                 case 0:
571                         memset(&term.c.attr, 0, sizeof(term.c.attr));
572                         term.c.attr.fg = DefaultFG;
573                         term.c.attr.bg = DefaultBG;
574                         break;
575                 case 1:
576                         term.c.attr.mode |= ATbold;      
577                         break;
578                 case 4: 
579                         term.c.attr.mode |= ATunderline;
580                         break;
581                 case 7: 
582                         term.c.attr.mode |= ATreverse;  
583                         break;
584                 case 8:
585                         term.c.hidden = CShide;
586                         break;
587                 case 22: 
588                         term.c.attr.mode &= ~ATbold;  
589                         break;
590                 case 24: 
591                         term.c.attr.mode &= ~ATunderline;
592                         break;
593                 case 27: 
594                         term.c.attr.mode &= ~ATreverse;  
595                         break;
596                 case 39:
597                         term.c.attr.fg = DefaultFG;
598                         break;
599                 case 49:
600                         term.c.attr.fg = DefaultBG;
601                         break;
602                 default:
603                         if(BETWEEN(attr[i], 30, 37))
604                                 term.c.attr.fg = attr[i] - 30;
605                         else if(BETWEEN(attr[i], 40, 47))
606                                 term.c.attr.bg = attr[i] - 40;
607                         break;
608                 }
609         }
610 }
611
612 void
613 tsetscroll(int t, int b) {
614         int temp;
615
616         LIMIT(t, 0, term.row-1);
617         LIMIT(b, 0, term.row-1);
618         if(t > b) {
619                 temp = t;
620                 t = b;
621                 b = temp;
622         }
623         term.top = t;
624         term.bot = b;    
625 }
626
627 void
628 eschandle(void) {
629         switch(escseq.pre) {
630         default:
631                 goto unknown_seq;
632         case '[':
633                 switch(escseq.mode) {
634                 default:
635                 unknown_seq:
636                         fprintf(stderr, "erresc: unknown sequence\n");
637                         escdump();
638                         break;
639                 case '@': /* Insert <n> blank char */
640                         DEFAULT(escseq.arg[0], 1);
641                         tinsertblank(escseq.arg[0]);
642                         break;
643                 case 'A': /* Cursor <n> Up */
644                 case 'e':
645                         DEFAULT(escseq.arg[0], 1);
646                         tmoveto(term.c.x, term.c.y-escseq.arg[0]);
647                         break;
648                 case 'B': /* Cursor <n> Down */
649                         DEFAULT(escseq.arg[0], 1);
650                         tmoveto(term.c.x, term.c.y+escseq.arg[0]);
651                         break;
652                 case 'C': /* Cursor <n> Forward */
653                 case 'a':
654                         DEFAULT(escseq.arg[0], 1);
655                         tmoveto(term.c.x+escseq.arg[0], term.c.y);
656                         break;
657                 case 'D': /* Cursor <n> Backward */
658                         DEFAULT(escseq.arg[0], 1);
659                         tmoveto(term.c.x-escseq.arg[0], term.c.y);
660                         break;
661                 case 'E': /* Cursor <n> Down and first col */
662                         DEFAULT(escseq.arg[0], 1);
663                         tmoveto(0, term.c.y+escseq.arg[0]);
664                         break;
665                 case 'F': /* Cursor <n> Up and first col */
666                         DEFAULT(escseq.arg[0], 1);
667                         tmoveto(0, term.c.y-escseq.arg[0]);
668                         break;
669                 case 'G': /* Move to <col> */
670                 case '`':
671                         DEFAULT(escseq.arg[0], 1);
672                         tmoveto(escseq.arg[0]-1, term.c.y);
673                         break;
674                 case 'H': /* Move to <row> <col> */
675                 case 'f':
676                         DEFAULT(escseq.arg[0], 1);
677                         DEFAULT(escseq.arg[1], 1);
678                         tmoveto(escseq.arg[1]-1, escseq.arg[0]-1);
679                         break;
680                 case 'J': /* Clear screen */
681                         switch(escseq.arg[0]) {
682                         case 0: /* below */
683                                 tclearregion(term.c.x, term.c.y, term.col-1, term.row-1);
684                                 break;
685                         case 1: /* above */
686                                 tclearregion(0, 0, term.c.x, term.c.y);
687                                 break;
688                         case 2: /* all */
689                                 tclearregion(0, 0, term.col-1, term.row-1);
690                                 break;                            
691                         }
692                         break;
693                 case 'K': /* Clear line */
694                         switch(escseq.arg[0]) {
695                         case 0: /* right */
696                                 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
697                                 break;
698                         case 1: /* left */
699                                 tclearregion(0, term.c.y, term.c.x, term.c.y);
700                                 break;
701                         case 2: /* all */
702                                 tclearregion(0, term.c.y, term.col-1, term.c.y);
703                                 break;
704                         }
705                         break;
706                 case 'L': /* Insert <n> blank lines */
707                         DEFAULT(escseq.arg[0], 1);
708                         tinsertblankline(escseq.arg[0]);
709                         break;
710                 case 'l':
711                         if(escseq.priv && escseq.arg[0] == 25)
712                                 term.c.hidden = 1;
713                         break;
714                 case 'M': /* Delete <n> lines */
715                         DEFAULT(escseq.arg[0], 1);
716                         tdeleteline(escseq.arg[0]);
717                         break;
718                 case 'P': /* Delete <n> char */
719                         DEFAULT(escseq.arg[0], 1);
720                         tdeletechar(escseq.arg[0]);
721                         break;
722                 case 'd': /* Move to <row> */
723                         DEFAULT(escseq.arg[0], 1);
724                         tmoveto(term.c.x, escseq.arg[0]-1);
725                         break;
726                 case 'h': /* Set terminal mode */
727                         if(escseq.priv && escseq.arg[0] == 25)
728                                 term.c.hidden = 0;
729                         break;
730                 case 'm': /* Terminal attribute (color) */
731                         tsetattr(escseq.arg, escseq.narg);
732                         break;
733                 case 'r':
734                         if(escseq.priv)
735                                 ;
736                         else {
737                                 DEFAULT(escseq.arg[0], 1);
738                                 DEFAULT(escseq.arg[1], term.row);
739                                 tsetscroll(escseq.arg[0]-1, escseq.arg[1]-1);
740                         }
741                         break;
742                 case 's': /* Save cursor position */
743                         tcpos(CSsave);
744                         break;
745                 case 'u': /* Load cursor position */
746                         tcpos(CSload);
747                         break;
748                 }
749                 break;
750         }
751 }
752
753 void
754 escdump(void) { 
755         int i;
756         printf("rawbuf  : %s\n", escseq.buf);
757         printf("prechar : %c\n", escseq.pre);
758         printf("private : %c\n", escseq.priv ? '?' : ' ');
759         printf("narg    : %d\n", escseq.narg);
760         if(escseq.narg)
761                 for(i = 0; i < escseq.narg; i++)
762                         printf("\targ %d = %d\n", i, escseq.arg[i]);
763         printf("mode    : %c\n", escseq.mode);
764 }
765
766 void
767 escreset(void) {
768         memset(&escseq, 0, sizeof(escseq));
769 }
770
771 void
772 tputtab(void) {
773         int space = TAB - term.c.x % TAB;
774         
775         if(term.c.x + space >= term.col)
776                 space--;
777         
778         for(; space > 0; space--)
779                 tcursor(CSright);
780 }
781
782 void
783 tputc(char c) {
784         static int inesc = 0;
785 #if 0
786         dump(c);
787 #endif  
788         /* start of escseq */
789         if(c == '\033')
790                 escreset(), inesc = 1;
791         else if(inesc) {
792                 inesc = escaddc(c);
793         } /* normal char */ 
794         else switch(c) { 
795                 default:
796                         tsetchar(c);
797                         tcursor(CSright);
798                         break;
799                 case '\t':
800                         tputtab();
801                         break;
802                 case '\b':
803                         tcursor(CSleft);
804                         break;
805                 case '\r':
806                         tmoveto(0, term.c.y);
807                         break;
808                 case '\n':
809                         tnewline();
810                         break;
811                 case '\a':
812                         xbell();
813                         break;
814         }
815 }
816
817 void
818 tputs(char *s, int len) { 
819         for(; len > 0; len--)
820                 tputc(*s++);
821 }
822
823 void
824 tresize(int col, int row) {
825         int i;
826         Line *line;
827         int minrow = MIN(row, term.row);
828         int mincol = MIN(col, term.col);
829
830         if(col < 1 || row < 1)
831                 return;
832         /* alloc */
833         line = calloc(row, sizeof(Line));
834         for(i = 0 ; i < row; i++)
835                 line[i] = calloc(col, sizeof(Glyph));
836         /* copy */
837         for(i = 0 ; i < minrow; i++)
838                 memcpy(line[i], term.line[i], mincol * sizeof(Glyph));
839         /* free */
840         for(i = 0; i < term.row; i++)
841                 free(term.line[i]);
842         free(term.line);
843         
844         LIMIT(term.c.x, 0, col-1);
845         LIMIT(term.c.y, 0, row-1);
846         LIMIT(term.top, 0, row-1);
847         LIMIT(term.bot, 0, row-1);
848         
849         term.bot = row-1;
850         term.line = line;
851         term.col = col, term.row = row;
852 }
853
854 unsigned long
855 xgetcol(const char *s) {
856         XColor color;
857         Colormap cmap = DefaultColormap(xw.dis, xw.scr);
858
859         if(!XAllocNamedColor(xw.dis, cmap, s, &color, &color)) {
860                 color.pixel = WhitePixel(xw.dis, xw.scr);
861                 fprintf(stderr, "Could not allocate color '%s'\n", s);
862         }
863         return color.pixel;
864 }
865
866 void
867 xclear(int x1, int y1, int x2, int y2) {
868         XClearArea(xw.dis, xw.win, 
869                         x1 * xw.cw, y1 * xw.ch, 
870                         (x2-x1+1) * xw.cw, (y2-y1+1) * xw.ch, 
871                         False);
872 }
873
874 void
875 xscroll(void) {
876         int srcy = (term.top+1) * xw.ch;
877         int dsty = term.top * xw.ch;
878         int height = (term.bot-term.top) * xw.ch;
879
880         xcursor(CShide);
881         XCopyArea(xw.dis, xw.win, xw.win, dc.gc, 0, srcy, xw.w, height, 0, dsty);
882         xclear(0, term.bot, term.col-1, term.bot);
883 }
884
885 void
886 xinit(void) {
887         XGCValues values;
888         unsigned long valuemask;
889         XClassHint chint;
890         XWMHints wmhint;
891         XSizeHints shint;
892         char *args[] = {NULL};
893         int i;
894
895         xw.dis = XOpenDisplay(NULL);
896         xw.scr = XDefaultScreen(xw.dis);
897         if(!xw.dis)
898                 die("Can't open display\n");
899         
900         /* font */
901         if(!(dc.font = XLoadQueryFont(xw.dis, FONT)))
902                 die("Can't load font %s\n", FONT);
903
904         xw.cw = dc.font->max_bounds.rbearing - dc.font->min_bounds.lbearing;
905         xw.ch = dc.font->ascent + dc.font->descent + LINESPACE;
906
907         /* colors */
908         for(i = 0; i < LEN(colorname); i++)
909                 dc.col[i] = xgetcol(colorname[i]);
910
911         term.c.attr.fg = DefaultFG;
912         term.c.attr.bg = DefaultBG;
913         term.c.attr.mode = ATnone;
914         /* windows */
915         xw.h = term.row * xw.ch;
916         xw.w = term.col * xw.cw;
917         /* XXX: this BORDER is useless after the first resize, handle it in xdraws() */
918         xw.win = XCreateSimpleWindow(xw.dis, XRootWindow(xw.dis, xw.scr), 0, 0,
919                         xw.w, xw.h, BORDER, 
920                         dc.col[DefaultBG],
921                         dc.col[DefaultBG]);
922         /* gc */
923         values.foreground = XWhitePixel(xw.dis, xw.scr);
924         values.font = dc.font->fid;
925         valuemask = GCForeground | GCFont;
926         dc.gc = XCreateGC(xw.dis, xw.win, valuemask, &values);
927         XMapWindow(xw.dis, xw.win);
928         /* wm stuff */
929         chint.res_name = TNAME, chint.res_class = TNAME;
930         wmhint.input = 1, wmhint.flags = InputHint;
931         shint.height_inc = xw.ch, shint.width_inc = xw.cw;
932         shint.height = xw.h, shint.width = xw.w;
933         shint.flags = PSize | PResizeInc;
934         XSetWMProperties(xw.dis, xw.win, NULL, NULL, &args[0], 0, &shint, &wmhint, &chint);
935         XStoreName(xw.dis, xw.win, TNAME);
936         XSync(xw.dis, 0);
937 }
938
939 void
940 xdraws (char *s, Glyph base, int x, int y, int len) {
941         unsigned long xfg, xbg;
942         int winx = x*xw.cw, winy = y*xw.ch + dc.font->ascent, width = len*xw.cw;
943         if(base.mode & ATreverse)
944                 xfg = dc.col[base.bg], xbg = dc.col[base.fg];
945         else
946                 xfg = dc.col[base.fg], xbg = dc.col[base.bg];
947
948         XSetBackground(xw.dis, dc.gc, xbg);
949         XSetForeground(xw.dis, dc.gc, xfg);
950         XDrawImageString(xw.dis, xw.win, dc.gc, winx, winy, s, len);
951         
952         if(base.mode & ATunderline)
953                 XDrawLine(xw.dis, xw.win, dc.gc, winx, winy+1, winx+width-1, winy+1);
954 }
955
956 void
957 xdrawc(int x, int y, Glyph g) {
958         XRectangle r = { x * xw.cw, y * xw.ch, xw.cw, xw.ch };
959         unsigned long xfg, xbg;
960
961         /* reverse video */
962         if(g.mode & ATreverse)
963                 xfg = dc.col[g.bg], xbg = dc.col[g.fg];
964         else
965                 xfg = dc.col[g.fg], xbg = dc.col[g.bg];
966         /* background */
967         XSetBackground(xw.dis, dc.gc, xbg);
968         XSetForeground(xw.dis, dc.gc, xfg);
969         XDrawImageString(xw.dis, xw.win, dc.gc, r.x, r.y+dc.font->ascent, &g.c, 1);
970 }
971
972 void
973 xcursor(int mode) {
974         static int oldx = 0;
975         static int oldy = 0;
976         Glyph g = {' ', ATnone, DefaultBG, DefaultCS, 0};
977         
978         LIMIT(oldx, 0, term.col-1);
979         LIMIT(oldy, 0, term.row-1);
980         
981         if(term.line[term.c.y][term.c.x].state & CRset)
982                 g.c = term.line[term.c.y][term.c.x].c;
983         /* remove the old cursor */
984         if(term.line[oldy][oldx].state & CRset)
985                 xdrawc(oldx, oldy, term.line[oldy][oldx]);
986         else 
987                 xclear(oldx, oldy, oldx, oldy);
988         /* draw the new one */
989         if(mode == CSdraw) {
990                 xdrawc(term.c.x, term.c.y, g);
991                 oldx = term.c.x, oldy = term.c.y;
992         }
993 }
994
995 void
996 draw(int redraw_all) {
997         int i, x, y, ox;
998         Glyph base, new;
999         char buf[MAXDRAWBUF];
1000         
1001         for(y = 0; y < term.row; y++) {
1002                 base = term.line[y][0];
1003                 i = ox = 0;
1004                 for(x = 0; x < term.col; x++) {
1005                         new = term.line[y][x];
1006                         if(!ATTRCMP(base, new) && i < MAXDRAWBUF)
1007                                 buf[i++] = new.c;
1008                         else {
1009                                 xdraws(buf, base, ox, y, i);
1010                                 buf[0] = new.c;
1011                                 i = 1;
1012                                 ox = x;
1013                                 base = new;
1014                         }
1015                 }
1016                 xdraws(buf, base, ox, y, i);
1017         }
1018         xcursor(CSdraw);
1019 }
1020
1021 void
1022 expose(XEvent *ev) {
1023         draw(SCredraw);
1024 }
1025
1026 char *
1027 kmap(KeySym k) {
1028         int i;
1029         for(i = 0; i < LEN(key); i++)
1030                 if(key[i].k == k)
1031                         return (char*)key[i].s;
1032         return NULL;
1033 }
1034
1035 void
1036 kpress(XEvent *ev) {
1037         XKeyEvent *e = &ev->xkey;
1038         KeySym ksym;
1039         char buf[32];
1040         char *customkey;
1041         int len;
1042         int meta;
1043         int shift;
1044
1045         meta  = e->state & Mod1Mask;
1046         shift = e->state & ShiftMask;
1047         len = XLookupString(e, buf, sizeof(buf), &ksym, NULL);
1048
1049         if(customkey = kmap(ksym))
1050                 ttywrite(customkey, strlen(customkey));
1051         else if(len > 0) {
1052                 buf[sizeof(buf)-1] = '\0';
1053                 if(meta && len == 1)
1054                         ttywrite("\033", 1);
1055                 ttywrite(buf, len);
1056         } else
1057                 switch(ksym) {
1058                 case XK_Insert:
1059                         if(shift)
1060                                 /* XXX: paste X clipboard */;
1061                         break;
1062                 default:
1063                         fprintf(stderr, "errkey: %d\n", (int)ksym);
1064                         break;
1065                 }
1066 }
1067
1068 void
1069 resize(XEvent *e) {
1070         int col, row;
1071         col = e->xconfigure.width / xw.cw;
1072         row = e->xconfigure.height / xw.ch;
1073         
1074         if(term.col != col || term.row != row) {
1075                 tresize(col, row);
1076                 ttyresize(col, row);
1077                 xw.w = e->xconfigure.width;
1078                 xw.h = e->xconfigure.height;
1079                 draw(SCredraw);
1080         }
1081 }
1082
1083 void
1084 run(void) {
1085         XEvent ev;
1086         fd_set rfd;
1087         int xfd = XConnectionNumber(xw.dis);
1088
1089         running = 1;
1090         XSelectInput(xw.dis, xw.win, ExposureMask | KeyPressMask | StructureNotifyMask);
1091         XResizeWindow(xw.dis, xw.win, xw.w , xw.h); /* fix resize bug in wmii (?) */
1092
1093         while(running) {
1094                 FD_ZERO(&rfd);
1095                 FD_SET(cmdfd, &rfd);
1096                 FD_SET(xfd, &rfd);
1097                 if(select(MAX(xfd, cmdfd)+1, &rfd, NULL, NULL, NULL) == -1) {
1098                         if(errno == EINTR)
1099                                 continue;
1100                         die("select failed: %s\n", SERRNO);
1101                 }
1102                 if(FD_ISSET(cmdfd, &rfd)) {
1103                         ttyread();
1104                         draw(SCupdate);
1105                 }
1106                 while(XPending(xw.dis)) {
1107                         XNextEvent(xw.dis, &ev);
1108                         if(handler[ev.type])
1109                                 (handler[ev.type])(&ev);
1110                 }
1111         }
1112 }
1113
1114 int
1115 main(int argc, char *argv[]) {
1116         if(argc == 2 && !strncmp("-v", argv[1], 3))
1117                 die("st-" VERSION ", © 2009 st engineers\n");
1118         else if(argc != 1)
1119                 die("usage: st [-v]\n");
1120         setlocale(LC_CTYPE, "");
1121         tnew(80, 24);
1122         ttynew();
1123         xinit();
1124         run();
1125         return 0;
1126 }