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