Xinqi Bao's Git

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