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