Xinqi Bao's Git

d928f0d6c58d0115711d69d5cb5330c8bd450ca6
[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 #if defined(__linux)
24 #include <pty.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__)
26 #include <util.h>
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
28 #include <libutil.h>
29 #endif
30
31 /* Arbitrary sizes */
32 #define ESC_TITLE_SIZ 256
33 #define ESC_BUF_SIZ 256
34 #define ESC_ARG_SIZ 16
35 #define DRAW_BUF_SIZ 1024
36
37 #define SERRNO strerror(errno)
38 #define MIN(a, b) ((a) < (b) ? (a) : (b))
39 #define MAX(a, b) ((a) < (b) ? (b) : (a))
40 #define LEN(a) (sizeof(a) / sizeof(a[0]))
41 #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
42 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
43 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
44 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
45 #define IS_SET(flag) (term.mode & (flag))
46
47 /* Attribute, Cursor, Character state, Terminal mode, Screen draw mode */
48 enum { ATTR_NULL=0 , ATTR_REVERSE=1 , ATTR_UNDERLINE=2, ATTR_BOLD=4, ATTR_GFX=8 };
49 enum { CURSOR_UP, CURSOR_DOWN, CURSOR_LEFT, CURSOR_RIGHT,
50 CURSOR_SAVE, CURSOR_LOAD };
51 enum { CURSOR_DEFAULT = 0, CURSOR_HIDE = 1, CURSOR_WRAPNEXT = 2 };
52 enum { GLYPH_SET=1, GLYPH_DIRTY=2 };
53 enum { MODE_WRAP=1, MODE_INSERT=2, MODE_APPKEYPAD=4, MODE_ALTSCREEN=8 };
54 enum { ESC_START=1, ESC_CSI=2, ESC_OSC=4, ESC_TITLE=8, ESC_ALTCHARSET=16 };
55 enum { SCREEN_UPDATE, SCREEN_REDRAW };
56
57 typedef struct {
58 char c; /* character code */
59 char mode; /* attribute flags */
60 int fg; /* foreground */
61 int bg; /* background */
62 char state; /* state flags */
63 } Glyph;
64
65 typedef Glyph* Line;
66
67 typedef struct {
68 Glyph attr; /* current char attributes */
69 int x;
70 int y;
71 char state;
72 } TCursor;
73
74 /* CSI Escape sequence structs */
75 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
76 typedef struct {
77 char buf[ESC_BUF_SIZ]; /* raw string */
78 int len; /* raw string length */
79 char priv;
80 int arg[ESC_ARG_SIZ];
81 int narg; /* nb of args */
82 char mode;
83 } CSIEscape;
84
85 /* Internal representation of the screen */
86 typedef struct {
87 int row; /* nb row */
88 int col; /* nb col */
89 Line* line; /* screen */
90 Line* alt; /* alternate screen */
91 TCursor c; /* cursor */
92 int top; /* top scroll limit */
93 int bot; /* bottom scroll limit */
94 int mode; /* terminal mode flags */
95 int esc; /* escape state flags */
96 char title[ESC_TITLE_SIZ];
97 int titlelen;
98 } Term;
99
100 /* Purely graphic info */
101 typedef struct {
102 Display* dis;
103 Window win;
104 Pixmap buf;
105 int scr;
106 int w; /* window width */
107 int h; /* window height */
108 int bufw; /* pixmap width */
109 int bufh; /* pixmap height */
110 int ch; /* char height */
111 int cw; /* char width */
112 int hasfocus;
113 } XWindow;
114
115 typedef struct {
116 KeySym k;
117 char s[ESC_BUF_SIZ];
118 } Key;
119
120 /* Drawing Context */
121 typedef struct {
122 unsigned long col[256];
123 XFontStruct* font;
124 XFontStruct* bfont;
125 GC gc;
126 } DC;
127
128 #include "config.h"
129
130 static void die(const char *errstr, ...);
131 static void draw(int);
132 static void execsh(void);
133 static void sigchld(int);
134 static void run(void);
135
136 static void csidump(void);
137 static void csihandle(void);
138 static void csiparse(void);
139 static void csireset(void);
140
141 static void tclearregion(int, int, int, int);
142 static void tcursor(int);
143 static void tdeletechar(int);
144 static void tdeleteline(int);
145 static void tinsertblank(int);
146 static void tinsertblankline(int);
147 static void tmoveto(int, int);
148 static void tnew(int, int);
149 static void tnewline(void);
150 static void tputtab(void);
151 static void tputc(char);
152 static void tputs(char*, int);
153 static void treset(void);
154 static void tresize(int, int);
155 static void tscrollup(int);
156 static void tscrolldown(int);
157 static void tsetattr(int*, int);
158 static void tsetchar(char);
159 static void tsetscroll(int, int);
160 static void tswapscreen(void);
161
162 static void ttynew(void);
163 static void ttyread(void);
164 static void ttyresize(int, int);
165 static void ttywrite(const char *, size_t);
166
167 static void xdraws(char *, Glyph, int, int, int);
168 static void xhints(void);
169 static void xclear(int, int, int, int);
170 static void xdrawcursor(void);
171 static void xinit(void);
172 static void xloadcols(void);
173 static void xseturgency(int);
174
175 static void expose(XEvent *);
176 static char* kmap(KeySym);
177 static void kpress(XEvent *);
178 static void resize(XEvent *);
179 static void focus(XEvent *);
180
181
182 static void (*handler[LASTEvent])(XEvent *) = {
183 [KeyPress] = kpress,
184 [Expose] = expose,
185 [ConfigureNotify] = resize,
186 [FocusIn] = focus,
187 [FocusOut] = focus,
188 };
189
190 /* Globals */
191 static DC dc;
192 static XWindow xw;
193 static Term term;
194 static CSIEscape escseq;
195 static int cmdfd;
196 static pid_t pid;
197
198 #ifdef DEBUG
199 void
200 tdump(void) {
201 int row, col;
202 Glyph c;
203
204 for(row = 0; row < term.row; row++) {
205 for(col = 0; col < term.col; col++) {
206 if(col == term.c.x && row == term.c.y)
207 putchar('#');
208 else {
209 c = term.line[row][col];
210 putchar(c.state & GLYPH_SET ? c.c : '.');
211 }
212 }
213 putchar('\n');
214 }
215 }
216 #endif
217
218 void
219 die(const char *errstr, ...) {
220 va_list ap;
221
222 va_start(ap, errstr);
223 vfprintf(stderr, errstr, ap);
224 va_end(ap);
225 exit(EXIT_FAILURE);
226 }
227
228 void
229 execsh(void) {
230 char *args[3] = {getenv("SHELL"), "-i", NULL};
231 DEFAULT(args[0], "/bin/sh"); /* if getenv() failed */
232 putenv("TERM=" TNAME);
233 execvp(args[0], args);
234 }
235
236 void
237 sigchld(int a) {
238 int stat = 0;
239 if(waitpid(pid, &stat, 0) < 0)
240 die("Waiting for pid %hd failed: %s\n", pid, SERRNO);
241 if(WIFEXITED(stat))
242 exit(WEXITSTATUS(stat));
243 else
244 exit(EXIT_FAILURE);
245 }
246
247 void
248 ttynew(void) {
249 int m, s;
250
251 /* seems to work fine on linux, openbsd and freebsd */
252 struct winsize w = {term.row, term.col, 0, 0};
253 if(openpty(&m, &s, NULL, NULL, &w) < 0)
254 die("openpty failed: %s\n", SERRNO);
255
256 switch(pid = fork()) {
257 case -1:
258 die("fork failed\n");
259 break;
260 case 0:
261 setsid(); /* create a new process group */
262 dup2(s, STDIN_FILENO);
263 dup2(s, STDOUT_FILENO);
264 dup2(s, STDERR_FILENO);
265 if(ioctl(s, TIOCSCTTY, NULL) < 0)
266 die("ioctl TIOCSCTTY failed: %s\n", SERRNO);
267 close(s);
268 close(m);
269 execsh();
270 break;
271 default:
272 close(s);
273 cmdfd = m;
274 signal(SIGCHLD, sigchld);
275 }
276 }
277
278 void
279 dump(char c) {
280 static int col;
281 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
282 if(++col % 10 == 0)
283 fprintf(stderr, "\n");
284 }
285
286 void
287 ttyread(void) {
288 char buf[BUFSIZ] = {0};
289 int ret;
290
291 if((ret = read(cmdfd, buf, BUFSIZ)) < 0)
292 die("Couldn't read from shell: %s\n", SERRNO);
293 else
294 tputs(buf, ret);
295 }
296
297 void
298 ttywrite(const char *s, size_t n) {
299 if(write(cmdfd, s, n) == -1)
300 die("write error on tty: %s\n", SERRNO);
301 }
302
303 void
304 ttyresize(int x, int y) {
305 struct winsize w;
306
307 w.ws_row = term.row;
308 w.ws_col = term.col;
309 w.ws_xpixel = w.ws_ypixel = 0;
310 if(ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
311 fprintf(stderr, "Couldn't set window size: %s\n", SERRNO);
312 }
313
314 void
315 tcursor(int mode) {
316 static TCursor c;
317
318 if(mode == CURSOR_SAVE)
319 c = term.c;
320 else if(mode == CURSOR_LOAD)
321 term.c = c, tmoveto(c.x, c.y);
322 }
323
324 void
325 treset(void) {
326 term.c = (TCursor){{
327 .mode = ATTR_NULL,
328 .fg = DefaultFG,
329 .bg = DefaultBG
330 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
331
332 term.top = 0, term.bot = term.row - 1;
333 term.mode = MODE_WRAP;
334 tclearregion(0, 0, term.col-1, term.row-1);
335 }
336
337 void
338 tnew(int col, int row) {
339 /* set screen size */
340 term.row = row, term.col = col;
341 term.line = malloc(term.row * sizeof(Line));
342 term.alt = malloc(term.row * sizeof(Line));
343 for(row = 0 ; row < term.row; row++) {
344 term.line[row] = malloc(term.col * sizeof(Glyph));
345 term.alt [row] = malloc(term.col * sizeof(Glyph));
346 }
347 /* setup screen */
348 treset();
349 }
350
351 void
352 tswapscreen(void) {
353 Line* tmp = term.line;
354 term.line = term.alt;
355 term.alt = tmp;
356 term.mode ^= MODE_ALTSCREEN;
357 }
358
359 void
360 tscrolldown (int n) {
361 int i;
362 Line temp;
363
364 LIMIT(n, 0, term.bot-term.top+1);
365
366 for(i = 0; i < n; i++)
367 memset(term.line[term.bot-i], 0, term.col*sizeof(Glyph));
368
369 for(i = term.bot; i >= term.top+n; i--) {
370 temp = term.line[i];
371 term.line[i] = term.line[i-n];
372 term.line[i-n] = temp;
373 }
374 }
375
376 void
377 tscrollup (int n) {
378 int i;
379 Line temp;
380 LIMIT(n, 0, term.bot-term.top+1);
381
382 for(i = 0; i < n; i++)
383 memset(term.line[term.top+i], 0, term.col*sizeof(Glyph));
384
385 for(i = term.top; i <= term.bot-n; i++) {
386 temp = term.line[i];
387 term.line[i] = term.line[i+n];
388 term.line[i+n] = temp;
389 }
390 }
391
392 void
393 tnewline(void) {
394 int y = term.c.y + 1;
395 if(y > term.bot)
396 tscrollup(1), y = term.bot;
397 tmoveto(0, y);
398 }
399
400 void
401 csiparse(void) {
402 /* int noarg = 1; */
403 char *p = escseq.buf;
404
405 escseq.narg = 0;
406 if(*p == '?')
407 escseq.priv = 1, p++;
408
409 while(p < escseq.buf+escseq.len) {
410 while(isdigit(*p)) {
411 escseq.arg[escseq.narg] *= 10;
412 escseq.arg[escseq.narg] += *p++ - '0'/*, noarg = 0 */;
413 }
414 if(*p == ';' && escseq.narg+1 < ESC_ARG_SIZ)
415 escseq.narg++, p++;
416 else {
417 escseq.mode = *p;
418 escseq.narg++;
419 return;
420 }
421 }
422 }
423
424 void
425 tmoveto(int x, int y) {
426 LIMIT(x, 0, term.col-1);
427 LIMIT(y, 0, term.row-1);
428 term.c.state &= ~CURSOR_WRAPNEXT;
429 term.c.x = x;
430 term.c.y = y;
431 }
432
433 void
434 tsetchar(char c) {
435 term.line[term.c.y][term.c.x] = term.c.attr;
436 term.line[term.c.y][term.c.x].c = c;
437 term.line[term.c.y][term.c.x].state |= GLYPH_SET;
438 }
439
440 void
441 tclearregion(int x1, int y1, int x2, int y2) {
442 int y, temp;
443
444 if(x1 > x2)
445 temp = x1, x1 = x2, x2 = temp;
446 if(y1 > y2)
447 temp = y1, y1 = y2, y2 = temp;
448
449 LIMIT(x1, 0, term.col-1);
450 LIMIT(x2, 0, term.col-1);
451 LIMIT(y1, 0, term.row-1);
452 LIMIT(y2, 0, term.row-1);
453
454 for(y = y1; y <= y2; y++)
455 memset(&term.line[y][x1], 0, sizeof(Glyph)*(x2-x1+1));
456 }
457
458 void
459 tdeletechar(int n) {
460 int src = term.c.x + n;
461 int dst = term.c.x;
462 int size = term.col - src;
463
464 if(src >= term.col) {
465 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
466 return;
467 }
468 memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph));
469 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
470 }
471
472 void
473 tinsertblank(int n) {
474 int src = term.c.x;
475 int dst = src + n;
476 int size = term.col - dst;
477
478 if(dst >= term.col) {
479 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
480 return;
481 }
482 memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph));
483 tclearregion(src, term.c.y, dst - 1, term.c.y);
484 }
485
486 void
487 tinsertblankline(int n) {
488 int i;
489 Line blank;
490 int bot = term.bot;
491
492 if(term.c.y > term.bot)
493 bot = term.row - 1;
494 else if(term.c.y < term.top)
495 bot = term.top - 1;
496 if(term.c.y + n >= bot) {
497 tclearregion(0, term.c.y, term.col-1, bot);
498 return;
499 }
500 for(i = bot; i >= term.c.y+n; i--) {
501 /* swap deleted line <-> blanked line */
502 blank = term.line[i];
503 term.line[i] = term.line[i-n];
504 term.line[i-n] = blank;
505 /* blank it */
506 memset(blank, 0, term.col * sizeof(Glyph));
507 }
508 }
509
510 void
511 tdeleteline(int n) {
512 int i;
513 Line blank;
514 int bot = term.bot;
515
516 if(term.c.y > term.bot)
517 bot = term.row - 1;
518 else if(term.c.y < term.top)
519 bot = term.top - 1;
520 if(term.c.y + n >= bot) {
521 tclearregion(0, term.c.y, term.col-1, bot);
522 return;
523 }
524 for(i = term.c.y; i <= bot-n; i++) {
525 /* swap deleted line <-> blanked line */
526 blank = term.line[i];
527 term.line[i] = term.line[i+n];
528 term.line[i+n] = blank;
529 /* blank it */
530 memset(blank, 0, term.col * sizeof(Glyph));
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 term.c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
542 term.c.attr.fg = DefaultFG;
543 term.c.attr.bg = DefaultBG;
544 break;
545 case 1:
546 term.c.attr.mode |= ATTR_BOLD;
547 break;
548 case 4:
549 term.c.attr.mode |= ATTR_UNDERLINE;
550 break;
551 case 7:
552 term.c.attr.mode |= ATTR_REVERSE;
553 break;
554 case 22:
555 term.c.attr.mode &= ~ATTR_BOLD;
556 break;
557 case 24:
558 term.c.attr.mode &= ~ATTR_UNDERLINE;
559 break;
560 case 27:
561 term.c.attr.mode &= ~ATTR_REVERSE;
562 break;
563 case 38:
564 if (i + 2 < l && attr[i + 1] == 5) {
565 i += 2;
566 if (BETWEEN(attr[i], 0, 255))
567 term.c.attr.fg = attr[i];
568 else
569 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[i]);
570 }
571 else
572 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[i]);
573 break;
574 case 39:
575 term.c.attr.fg = DefaultFG;
576 break;
577 case 48:
578 if (i + 2 < l && attr[i + 1] == 5) {
579 i += 2;
580 if (BETWEEN(attr[i], 0, 255))
581 term.c.attr.bg = attr[i];
582 else
583 fprintf(stderr, "erresc: bad bgcolor %d\n", attr[i]);
584 }
585 else
586 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[i]);
587 break;
588 case 49:
589 term.c.attr.bg = DefaultBG;
590 break;
591 default:
592 if(BETWEEN(attr[i], 30, 37))
593 term.c.attr.fg = attr[i] - 30;
594 else if(BETWEEN(attr[i], 40, 47))
595 term.c.attr.bg = attr[i] - 40;
596 else if(BETWEEN(attr[i], 90, 97))
597 term.c.attr.fg = attr[i] - 90 + 8;
598 else if(BETWEEN(attr[i], 100, 107))
599 term.c.attr.fg = attr[i] - 100 + 8;
600 else
601 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[i]);
602 break;
603 }
604 }
605 }
606
607 void
608 tsetscroll(int t, int b) {
609 int temp;
610
611 LIMIT(t, 0, term.row-1);
612 LIMIT(b, 0, term.row-1);
613 if(t > b) {
614 temp = t;
615 t = b;
616 b = temp;
617 }
618 term.top = t;
619 term.bot = b;
620 }
621
622 void
623 csihandle(void) {
624 switch(escseq.mode) {
625 default:
626 unknown:
627 printf("erresc: unknown csi ");
628 csidump();
629 /* die(""); */
630 break;
631 case '@': /* ICH -- Insert <n> blank char */
632 DEFAULT(escseq.arg[0], 1);
633 tinsertblank(escseq.arg[0]);
634 break;
635 case 'A': /* CUU -- Cursor <n> Up */
636 case 'e':
637 DEFAULT(escseq.arg[0], 1);
638 tmoveto(term.c.x, term.c.y-escseq.arg[0]);
639 break;
640 case 'B': /* CUD -- Cursor <n> Down */
641 DEFAULT(escseq.arg[0], 1);
642 tmoveto(term.c.x, term.c.y+escseq.arg[0]);
643 break;
644 case 'C': /* CUF -- Cursor <n> Forward */
645 case 'a':
646 DEFAULT(escseq.arg[0], 1);
647 tmoveto(term.c.x+escseq.arg[0], term.c.y);
648 break;
649 case 'D': /* CUB -- Cursor <n> Backward */
650 DEFAULT(escseq.arg[0], 1);
651 tmoveto(term.c.x-escseq.arg[0], term.c.y);
652 break;
653 case 'E': /* CNL -- Cursor <n> Down and first col */
654 DEFAULT(escseq.arg[0], 1);
655 tmoveto(0, term.c.y+escseq.arg[0]);
656 break;
657 case 'F': /* CPL -- Cursor <n> Up and first col */
658 DEFAULT(escseq.arg[0], 1);
659 tmoveto(0, term.c.y-escseq.arg[0]);
660 break;
661 case 'G': /* CHA -- Move to <col> */
662 case '`': /* XXX: HPA -- same? */
663 DEFAULT(escseq.arg[0], 1);
664 tmoveto(escseq.arg[0]-1, term.c.y);
665 break;
666 case 'H': /* CUP -- Move to <row> <col> */
667 case 'f': /* XXX: HVP -- same? */
668 DEFAULT(escseq.arg[0], 1);
669 DEFAULT(escseq.arg[1], 1);
670 tmoveto(escseq.arg[1]-1, escseq.arg[0]-1);
671 break;
672 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
673 case 'J': /* ED -- Clear screen */
674 switch(escseq.arg[0]) {
675 case 0: /* below */
676 tclearregion(term.c.x, term.c.y, term.col-1, term.row-1);
677 break;
678 case 1: /* above */
679 tclearregion(0, 0, term.c.x, term.c.y);
680 break;
681 case 2: /* all */
682 tclearregion(0, 0, term.col-1, term.row-1);
683 break;
684 case 3: /* XXX: erase saved lines (xterm) */
685 default:
686 goto unknown;
687 }
688 break;
689 case 'K': /* EL -- Clear line */
690 switch(escseq.arg[0]) {
691 case 0: /* right */
692 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
693 break;
694 case 1: /* left */
695 tclearregion(0, term.c.y, term.c.x, term.c.y);
696 break;
697 case 2: /* all */
698 tclearregion(0, term.c.y, term.col-1, term.c.y);
699 break;
700 }
701 break;
702 case 'S': /* SU -- Scroll <n> line up */
703 DEFAULT(escseq.arg[0], 1);
704 tscrollup(escseq.arg[0]);
705 break;
706 case 'T': /* SD -- Scroll <n> line down */
707 DEFAULT(escseq.arg[0], 1);
708 tscrolldown(escseq.arg[0]);
709 break;
710 case 'L': /* IL -- Insert <n> blank lines */
711 DEFAULT(escseq.arg[0], 1);
712 tinsertblankline(escseq.arg[0]);
713 break;
714 case 'l': /* RM -- Reset Mode */
715 if(escseq.priv) {
716 switch(escseq.arg[0]) {
717 case 1:
718 term.mode &= ~MODE_APPKEYPAD;
719 break;
720 case 7:
721 term.mode &= ~MODE_WRAP;
722 break;
723 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
724 break;
725 case 25:
726 term.c.state |= CURSOR_HIDE;
727 break;
728 case 1047:
729 if(IS_SET(MODE_ALTSCREEN)) {
730 tclearregion(0, 0, term.col-1, term.row-1);
731 tswapscreen();
732 }
733 break;
734 case 1048:
735 tcursor(CURSOR_LOAD);
736 break;
737 case 1049:
738 tcursor(CURSOR_LOAD);
739 if(IS_SET(MODE_ALTSCREEN)) {
740 tclearregion(0, 0, term.col-1, term.row-1);
741 tswapscreen();
742 }
743 break;
744 default:
745 goto unknown;
746 }
747 } else {
748 switch(escseq.arg[0]) {
749 case 4:
750 term.mode &= ~MODE_INSERT;
751 break;
752 default:
753 goto unknown;
754 }
755 }
756 break;
757 case 'M': /* DL -- Delete <n> lines */
758 DEFAULT(escseq.arg[0], 1);
759 tdeleteline(escseq.arg[0]);
760 break;
761 case 'X': /* ECH -- Erase <n> char */
762 DEFAULT(escseq.arg[0], 1);
763 tclearregion(term.c.x, term.c.y, term.c.x + escseq.arg[0], term.c.y);
764 break;
765 case 'P': /* DCH -- Delete <n> char */
766 DEFAULT(escseq.arg[0], 1);
767 tdeletechar(escseq.arg[0]);
768 break;
769 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
770 case 'd': /* VPA -- Move to <row> */
771 DEFAULT(escseq.arg[0], 1);
772 tmoveto(term.c.x, escseq.arg[0]-1);
773 break;
774 case 'h': /* SM -- Set terminal mode */
775 if(escseq.priv) {
776 switch(escseq.arg[0]) {
777 case 1:
778 term.mode |= MODE_APPKEYPAD;
779 break;
780 case 7:
781 term.mode |= MODE_WRAP;
782 break;
783 case 12: /* att610 -- Start blinking cursor (IGNORED) */
784 break;
785 case 25:
786 term.c.state &= ~CURSOR_HIDE;
787 break;
788 case 1047:
789 if(IS_SET(MODE_ALTSCREEN))
790 tclearregion(0, 0, term.col-1, term.row-1);
791 else
792 tswapscreen();
793 break;
794 case 1048:
795 tcursor(CURSOR_SAVE);
796 break;
797 case 1049:
798 tcursor(CURSOR_SAVE);
799 if(IS_SET(MODE_ALTSCREEN))
800 tclearregion(0, 0, term.col-1, term.row-1);
801 else
802 tswapscreen();
803 break;
804 default: goto unknown;
805 }
806 } else {
807 switch(escseq.arg[0]) {
808 case 4:
809 term.mode |= MODE_INSERT;
810 break;
811 default: goto unknown;
812 }
813 };
814 break;
815 case 'm': /* SGR -- Terminal attribute (color) */
816 tsetattr(escseq.arg, escseq.narg);
817 break;
818 case 'r': /* DECSTBM -- Set Scrolling Region */
819 if(escseq.priv)
820 goto unknown;
821 else {
822 DEFAULT(escseq.arg[0], 1);
823 DEFAULT(escseq.arg[1], term.row);
824 tsetscroll(escseq.arg[0]-1, escseq.arg[1]-1);
825 tmoveto(0, 0);
826 }
827 break;
828 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
829 tcursor(CURSOR_SAVE);
830 break;
831 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
832 tcursor(CURSOR_LOAD);
833 break;
834 }
835 }
836
837 void
838 csidump(void) {
839 int i;
840 printf("ESC [ %s", escseq.priv ? "? " : "");
841 if(escseq.narg)
842 for(i = 0; i < escseq.narg; i++)
843 printf("%d ", escseq.arg[i]);
844 if(escseq.mode)
845 putchar(escseq.mode);
846 putchar('\n');
847 }
848
849 void
850 csireset(void) {
851 memset(&escseq, 0, sizeof(escseq));
852 }
853
854 void
855 tputtab(void) {
856 int space = TAB - term.c.x % TAB;
857 tmoveto(term.c.x + space, term.c.y);
858 }
859
860 void
861 tputc(char c) {
862 if(term.esc & ESC_START) {
863 if(term.esc & ESC_CSI) {
864 escseq.buf[escseq.len++] = c;
865 if(BETWEEN(c, 0x40, 0x7E) || escseq.len >= ESC_BUF_SIZ) {
866 term.esc = 0;
867 csiparse(), csihandle();
868 }
869 } else if(term.esc & ESC_OSC) {
870 if(c == ';') {
871 term.titlelen = 0;
872 term.esc = ESC_START | ESC_TITLE;
873 }
874 } else if(term.esc & ESC_TITLE) {
875 if(c == '\a' || term.titlelen+1 >= ESC_TITLE_SIZ) {
876 term.esc = 0;
877 term.title[term.titlelen] = '\0';
878 XStoreName(xw.dis, xw.win, term.title);
879 } else {
880 term.title[term.titlelen++] = c;
881 }
882 } else if(term.esc & ESC_ALTCHARSET) {
883 switch(c) {
884 case '0': /* Line drawing crap */
885 term.c.attr.mode |= ATTR_GFX;
886 break;
887 case 'B': /* Back to regular text */
888 term.c.attr.mode &= ~ATTR_GFX;
889 break;
890 default:
891 printf("esc unhandled charset: ESC ( %c\n", c);
892 }
893 term.esc = 0;
894 } else {
895 switch(c) {
896 case '[':
897 term.esc |= ESC_CSI;
898 break;
899 case ']':
900 term.esc |= ESC_OSC;
901 break;
902 case '(':
903 term.esc |= ESC_ALTCHARSET;
904 break;
905 case 'D': /* IND -- Linefeed */
906 if(term.c.y == term.bot)
907 tscrollup(1);
908 else
909 tmoveto(term.c.x, term.c.y+1);
910 term.esc = 0;
911 break;
912 case 'E': /* NEL -- Next line */
913 tnewline();
914 term.esc = 0;
915 break;
916 case 'M': /* RI -- Reverse index */
917 if(term.c.y == term.top)
918 tscrolldown(1);
919 else
920 tmoveto(term.c.x, term.c.y-1);
921 term.esc = 0;
922 break;
923 case 'c': /* RIS -- Reset to inital state */
924 treset();
925 term.esc = 0;
926 break;
927 case '=': /* DECPAM -- Application keypad */
928 term.mode |= MODE_APPKEYPAD;
929 term.esc = 0;
930 break;
931 case '>': /* DECPNM -- Normal keypad */
932 term.mode &= ~MODE_APPKEYPAD;
933 term.esc = 0;
934 break;
935 case '7': /* DECSC -- Save Cursor*/
936 tcursor(CURSOR_SAVE);
937 term.esc = 0;
938 break;
939 case '8': /* DECRC -- Restore Cursor */
940 tcursor(CURSOR_LOAD);
941 term.esc = 0;
942 break;
943 default:
944 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", c, isprint(c)?c:'.');
945 term.esc = 0;
946 }
947 }
948 } else {
949 switch(c) {
950 case '\t':
951 tputtab();
952 break;
953 case '\b':
954 tmoveto(term.c.x-1, term.c.y);
955 break;
956 case '\r':
957 tmoveto(0, term.c.y);
958 break;
959 case '\n':
960 tnewline();
961 break;
962 case '\a':
963 if(!xw.hasfocus)
964 xseturgency(1);
965 break;
966 case '\033':
967 csireset();
968 term.esc = ESC_START;
969 break;
970 default:
971 if(IS_SET(MODE_WRAP) && term.c.state & CURSOR_WRAPNEXT)
972 tnewline();
973 tsetchar(c);
974 if(term.c.x+1 < term.col)
975 tmoveto(term.c.x+1, term.c.y);
976 else
977 term.c.state |= CURSOR_WRAPNEXT;
978 break;
979 }
980 }
981 }
982
983 void
984 tputs(char *s, int len) {
985 for(; len > 0; len--)
986 tputc(*s++);
987 }
988
989 void
990 tresize(int col, int row) {
991 int i;
992 int minrow = MIN(row, term.row);
993 int mincol = MIN(col, term.col);
994
995 if(col < 1 || row < 1)
996 return;
997
998 /* free uneeded rows */
999 for(i = row; i < term.row; i++) {
1000 free(term.line[i]);
1001 free(term.alt[i]);
1002 }
1003
1004 /* resize to new height */
1005 term.line = realloc(term.line, row * sizeof(Line));
1006 term.line = realloc(term.alt, row * sizeof(Line));
1007
1008 /* resize each row to new width, zero-pad if needed */
1009 for(i = 0; i < minrow; i++) {
1010 term.line[i] = realloc(term.line[i], col * sizeof(Glyph));
1011 term.alt[i] = realloc(term.alt[i], col * sizeof(Glyph));
1012 memset(term.line[i] + mincol, 0, (col - mincol) * sizeof(Glyph));
1013 memset(term.alt[i] + mincol, 0, (col - mincol) * sizeof(Glyph));
1014 }
1015
1016 /* allocate any new rows */
1017 for(/* i == minrow */; i < row; i++) {
1018 term.line[i] = calloc(col, sizeof(Glyph));
1019 term.alt [i] = calloc(col, sizeof(Glyph));
1020 }
1021
1022 /* update terminal size */
1023 term.col = col, term.row = row;
1024 /* make use of the LIMIT in tmoveto */
1025 tmoveto(term.c.x, term.c.y);
1026 /* reset scrolling region */
1027 tsetscroll(0, row-1);
1028 }
1029
1030 void
1031 xloadcols(void) {
1032 int i, r, g, b;
1033 XColor color;
1034 Colormap cmap = DefaultColormap(xw.dis, xw.scr);
1035 unsigned long white = WhitePixel(xw.dis, xw.scr);
1036
1037 for(i = 0; i < 16; i++) {
1038 if (!XAllocNamedColor(xw.dis, cmap, colorname[i], &color, &color)) {
1039 dc.col[i] = white;
1040 fprintf(stderr, "Could not allocate color '%s'\n", colorname[i]);
1041 } else
1042 dc.col[i] = color.pixel;
1043 }
1044
1045 /* same colors as xterm */
1046 for(r = 0; r < 6; r++)
1047 for(g = 0; g < 6; g++)
1048 for(b = 0; b < 6; b++) {
1049 color.red = r == 0 ? 0 : 0x3737 + 0x2828 * r;
1050 color.green = g == 0 ? 0 : 0x3737 + 0x2828 * g;
1051 color.blue = b == 0 ? 0 : 0x3737 + 0x2828 * b;
1052 if (!XAllocColor(xw.dis, cmap, &color)) {
1053 dc.col[i] = white;
1054 fprintf(stderr, "Could not allocate color %d\n", i);
1055 } else
1056 dc.col[i] = color.pixel;
1057 i++;
1058 }
1059
1060 for(r = 0; r < 24; r++, i++) {
1061 color.red = color.green = color.blue = 0x0808 + 0x0a0a * r;
1062 if (!XAllocColor(xw.dis, cmap, &color)) {
1063 dc.col[i] = white;
1064 fprintf(stderr, "Could not allocate color %d\n", i);
1065 } else
1066 dc.col[i] = color.pixel;
1067 }
1068 }
1069
1070 void
1071 xclear(int x1, int y1, int x2, int y2) {
1072 XSetForeground(xw.dis, dc.gc, dc.col[DefaultBG]);
1073 XFillRectangle(xw.dis, xw.buf, dc.gc,
1074 x1 * xw.cw, y1 * xw.ch,
1075 (x2-x1+1) * xw.cw, (y2-y1+1) * xw.ch);
1076 }
1077
1078 void
1079 xhints(void)
1080 {
1081 XClassHint class = {TNAME, TNAME};
1082 XWMHints wm = {.flags = InputHint, .input = 1};
1083 XSizeHints size = {
1084 .flags = PSize | PResizeInc | PBaseSize,
1085 .height = xw.h,
1086 .width = xw.w,
1087 .height_inc = xw.ch,
1088 .width_inc = xw.cw,
1089 .base_height = 2*BORDER,
1090 .base_width = 2*BORDER,
1091 };
1092 XSetWMProperties(xw.dis, xw.win, NULL, NULL, NULL, 0, &size, &wm, &class);
1093 }
1094
1095 void
1096 xinit(void) {
1097 if(!(xw.dis = XOpenDisplay(NULL)))
1098 die("Can't open display\n");
1099 xw.scr = XDefaultScreen(xw.dis);
1100
1101 /* font */
1102 if(!(dc.font = XLoadQueryFont(xw.dis, FONT)) || !(dc.bfont = XLoadQueryFont(xw.dis, BOLDFONT)))
1103 die("Can't load font %s\n", dc.font ? BOLDFONT : FONT);
1104
1105 /* XXX: Assuming same size for bold font */
1106 xw.cw = dc.font->max_bounds.rbearing - dc.font->min_bounds.lbearing;
1107 xw.ch = dc.font->ascent + dc.font->descent;
1108
1109 /* colors */
1110 xloadcols();
1111
1112 /* windows */
1113 xw.h = term.row * xw.ch + 2*BORDER;
1114 xw.w = term.col * xw.cw + 2*BORDER;
1115 xw.win = XCreateSimpleWindow(xw.dis, XRootWindow(xw.dis, xw.scr), 0, 0,
1116 xw.w, xw.h, 0,
1117 dc.col[DefaultBG],
1118 dc.col[DefaultBG]);
1119 xw.bufw = xw.w - 2*BORDER;
1120 xw.bufh = xw.h - 2*BORDER;
1121 xw.buf = XCreatePixmap(xw.dis, xw.win, xw.bufw, xw.bufh, XDefaultDepth(xw.dis, xw.scr));
1122 /* gc */
1123 dc.gc = XCreateGC(xw.dis, xw.win, 0, NULL);
1124 XMapWindow(xw.dis, xw.win);
1125 xhints();
1126 XStoreName(xw.dis, xw.win, "st");
1127 XSync(xw.dis, 0);
1128 }
1129
1130 void
1131 xdraws(char *s, Glyph base, int x, int y, int len) {
1132 unsigned long xfg, xbg;
1133 int winx = x*xw.cw, winy = y*xw.ch + dc.font->ascent, width = len*xw.cw;
1134 int i;
1135
1136 if(base.mode & ATTR_REVERSE)
1137 xfg = dc.col[base.bg], xbg = dc.col[base.fg];
1138 else
1139 xfg = dc.col[base.fg], xbg = dc.col[base.bg];
1140
1141 XSetBackground(xw.dis, dc.gc, xbg);
1142 XSetForeground(xw.dis, dc.gc, xfg);
1143
1144 if(base.mode & ATTR_GFX)
1145 for(i = 0; i < len; i++)
1146 s[i] = gfx[(int)s[i]];
1147
1148 XSetFont(xw.dis, dc.gc, base.mode & ATTR_BOLD ? dc.bfont->fid : dc.font->fid);
1149 XDrawImageString(xw.dis, xw.buf, dc.gc, winx, winy, s, len);
1150
1151 if(base.mode & ATTR_UNDERLINE)
1152 XDrawLine(xw.dis, xw.buf, dc.gc, winx, winy+1, winx+width-1, winy+1);
1153 }
1154
1155 void
1156 xdrawcursor(void) {
1157 static int oldx = 0;
1158 static int oldy = 0;
1159 Glyph g = {' ', ATTR_NULL, DefaultBG, DefaultCS, 0};
1160
1161 LIMIT(oldx, 0, term.col-1);
1162 LIMIT(oldy, 0, term.row-1);
1163
1164 if(term.line[term.c.y][term.c.x].state & GLYPH_SET)
1165 g.c = term.line[term.c.y][term.c.x].c;
1166
1167 /* remove the old cursor */
1168 if(term.line[oldy][oldx].state & GLYPH_SET)
1169 xdraws(&term.line[oldy][oldx].c, term.line[oldy][oldx], oldx, oldy, 1);
1170 else
1171 xclear(oldx, oldy, oldx, oldy);
1172
1173 /* draw the new one */
1174 if(!(term.c.state & CURSOR_HIDE) && xw.hasfocus) {
1175 xdraws(&g.c, g, term.c.x, term.c.y, 1);
1176 oldx = term.c.x, oldy = term.c.y;
1177 }
1178 }
1179
1180 #ifdef DEBUG
1181 /* basic drawing routines */
1182 void
1183 xdrawc(int x, int y, Glyph g) {
1184 XRectangle r = { x * xw.cw, y * xw.ch, xw.cw, xw.ch };
1185 XSetBackground(xw.dis, dc.gc, dc.col[g.bg]);
1186 XSetForeground(xw.dis, dc.gc, dc.col[g.fg]);
1187 XSetFont(xw.dis, dc.gc, g.mode & ATTR_BOLD ? dc.bfont->fid : dc.font->fid);
1188 XDrawImageString(xw.dis, xw.buf, dc.gc, r.x, r.y+dc.font->ascent, &g.c, 1);
1189 }
1190
1191 void
1192 draw(int dummy) {
1193 int x, y;
1194
1195 xclear(0, 0, term.col-1, term.row-1);
1196 for(y = 0; y < term.row; y++)
1197 for(x = 0; x < term.col; x++)
1198 if(term.line[y][x].state & GLYPH_SET)
1199 xdrawc(x, y, term.line[y][x]);
1200
1201 xdrawcursor();
1202 XCopyArea(xw.dis, xw.buf, xw.win, dc.gc, 0, 0, xw.bufw, xw.bufh, BORDER, BORDER);
1203 XFlush(xw.dis);
1204 }
1205
1206 #else
1207 /* optimized drawing routine */
1208 void
1209 draw(int redraw_all) {
1210 int i, x, y, ox;
1211 Glyph base, new;
1212 char buf[DRAW_BUF_SIZ];
1213
1214 XSetForeground(xw.dis, dc.gc, dc.col[DefaultBG]);
1215 XFillRectangle(xw.dis, xw.buf, dc.gc, 0, 0, xw.bufw, xw.bufh);
1216 for(y = 0; y < term.row; y++) {
1217 base = term.line[y][0];
1218 i = ox = 0;
1219 for(x = 0; x < term.col; x++) {
1220 new = term.line[y][x];
1221 if(i > 0 && (!(new.state & GLYPH_SET) || ATTRCMP(base, new) ||
1222 i >= DRAW_BUF_SIZ)) {
1223 xdraws(buf, base, ox, y, i);
1224 i = 0;
1225 }
1226 if(new.state & GLYPH_SET) {
1227 if(i == 0) {
1228 ox = x;
1229 base = new;
1230 }
1231 buf[i++] = new.c;
1232 }
1233 }
1234 if(i > 0)
1235 xdraws(buf, base, ox, y, i);
1236 }
1237 xdrawcursor();
1238 XCopyArea(xw.dis, xw.buf, xw.win, dc.gc, 0, 0, xw.bufw, xw.bufh, BORDER, BORDER);
1239 XFlush(xw.dis);
1240 }
1241
1242 #endif
1243
1244 void
1245 expose(XEvent *ev) {
1246 draw(SCREEN_REDRAW);
1247 }
1248
1249 void
1250 xseturgency(int add) {
1251 XWMHints *h = XGetWMHints(xw.dis, xw.win);
1252 h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
1253 XSetWMHints(xw.dis, xw.win, h);
1254 XFree(h);
1255 }
1256
1257 void
1258 focus(XEvent *ev) {
1259 if((xw.hasfocus = ev->type == FocusIn))
1260 xseturgency(0);
1261 draw(SCREEN_UPDATE);
1262 }
1263
1264 char*
1265 kmap(KeySym k) {
1266 int i;
1267 for(i = 0; i < LEN(key); i++)
1268 if(key[i].k == k)
1269 return (char*)key[i].s;
1270 return NULL;
1271 }
1272
1273 void
1274 kpress(XEvent *ev) {
1275 XKeyEvent *e = &ev->xkey;
1276 KeySym ksym;
1277 char buf[32];
1278 char *customkey;
1279 int len;
1280 int meta;
1281 int shift;
1282
1283 meta = e->state & Mod1Mask;
1284 shift = e->state & ShiftMask;
1285 len = XLookupString(e, buf, sizeof(buf), &ksym, NULL);
1286
1287 if((customkey = kmap(ksym)))
1288 ttywrite(customkey, strlen(customkey));
1289 else if(len > 0) {
1290 buf[sizeof(buf)-1] = '\0';
1291 if(meta && len == 1)
1292 ttywrite("\033", 1);
1293 ttywrite(buf, len);
1294 } else
1295 switch(ksym) {
1296 case XK_Up:
1297 case XK_Down:
1298 case XK_Left:
1299 case XK_Right:
1300 sprintf(buf, "\033%c%c", IS_SET(MODE_APPKEYPAD) ? 'O' : '[', "DACB"[ksym - XK_Left]);
1301 ttywrite(buf, 3);
1302 break;
1303 case XK_Insert:
1304 if(shift)
1305 draw(1), puts("draw!")/* XXX: paste X clipboard */;
1306 break;
1307 default:
1308 fprintf(stderr, "errkey: %d\n", (int)ksym);
1309 break;
1310 }
1311 }
1312
1313 void
1314 resize(XEvent *e) {
1315 int col, row;
1316
1317 if(e->xconfigure.width == xw.w && e->xconfigure.height == xw.h)
1318 return;
1319
1320 xw.w = e->xconfigure.width;
1321 xw.h = e->xconfigure.height;
1322 xw.bufw = xw.w - 2*BORDER;
1323 xw.bufh = xw.h - 2*BORDER;
1324 col = xw.bufw / xw.cw;
1325 row = xw.bufh / xw.ch;
1326 tresize(col, row);
1327 ttyresize(col, row);
1328 XFreePixmap(xw.dis, xw.buf);
1329 xw.buf = XCreatePixmap(xw.dis, xw.win, xw.bufw, xw.bufh, XDefaultDepth(xw.dis, xw.scr));
1330 draw(SCREEN_REDRAW);
1331 }
1332
1333 void
1334 run(void) {
1335 XEvent ev;
1336 fd_set rfd;
1337 int xfd = XConnectionNumber(xw.dis);
1338 long mask = ExposureMask | KeyPressMask | StructureNotifyMask | FocusChangeMask;
1339
1340 XSelectInput(xw.dis, xw.win, mask);
1341 XResizeWindow(xw.dis, xw.win, xw.w, xw.h); /* XXX: fix resize bug in wmii (?) */
1342
1343 while(1) {
1344 FD_ZERO(&rfd);
1345 FD_SET(cmdfd, &rfd);
1346 FD_SET(xfd, &rfd);
1347 if(select(MAX(xfd, cmdfd)+1, &rfd, NULL, NULL, NULL) < 0) {
1348 if(errno == EINTR)
1349 continue;
1350 die("select failed: %s\n", SERRNO);
1351 }
1352 if(FD_ISSET(cmdfd, &rfd)) {
1353 ttyread();
1354 draw(SCREEN_UPDATE);
1355 }
1356 while(XPending(xw.dis)) {
1357 XNextEvent(xw.dis, &ev);
1358 if(handler[ev.type])
1359 (handler[ev.type])(&ev);
1360 }
1361 }
1362 }
1363
1364 int
1365 main(int argc, char *argv[]) {
1366 if(argc == 2 && !strncmp("-v", argv[1], 3))
1367 die("st-" VERSION ", (c) 2010 st engineers\n");
1368 else if(argc != 1)
1369 die("usage: st [-v]\n");
1370 setlocale(LC_CTYPE, "");
1371 tnew(80, 24);
1372 ttynew();
1373 xinit();
1374 run();
1375 return 0;
1376 }