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