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