Xinqi Bao's Git

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