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