JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
compose key patch. thx federico luna.
[st.git] / st.c
1 /* See LICENSE for licence details. */
2 #define _XOPEN_SOURCE 600
3 #include <ctype.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <limits.h>
7 #include <locale.h>
8 #include <stdarg.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <signal.h>
13 #include <sys/ioctl.h>
14 #include <sys/select.h>
15 #include <sys/stat.h>
16 #include <sys/types.h>
17 #include <sys/wait.h>
18 #include <unistd.h>
19 #include <X11/Xlib.h>
20 #include <X11/keysym.h>
21 #include <X11/Xutil.h>
22
23 #if   defined(__linux)
24  #include <pty.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__)
26  #include <util.h>
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
28  #include <libutil.h>
29 #endif
30
31 #define USAGE \
32         "st-" VERSION ", (c) 2010 st engineers\n" \
33         "usage: st [-t title] [-e cmd] [-v]\n"
34
35 /* Arbitrary sizes */
36 #define ESC_TITLE_SIZ 256
37 #define ESC_BUF_SIZ   256
38 #define ESC_ARG_SIZ   16
39 #define DRAW_BUF_SIZ  1024
40
41 #define SERRNO strerror(errno)
42 #define MIN(a, b)  ((a) < (b) ? (a) : (b))
43 #define MAX(a, b)  ((a) < (b) ? (b) : (a))
44 #define LEN(a)     (sizeof(a) / sizeof(a[0]))
45 #define DEFAULT(a, b)     (a) = (a) ? (a) : (b)    
46 #define BETWEEN(x, a, b)  ((a) <= (x) && (x) <= (b))
47 #define LIMIT(x, a, b)    (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
48 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
49 #define IS_SET(flag) (term.mode & (flag))
50
51 /* Attribute, Cursor, Character state, Terminal mode, Screen draw mode */
52 enum { ATTR_NULL=0 , ATTR_REVERSE=1 , ATTR_UNDERLINE=2, ATTR_BOLD=4, ATTR_GFX=8 };
53 enum { CURSOR_UP, CURSOR_DOWN, CURSOR_LEFT, CURSOR_RIGHT,
54        CURSOR_SAVE, CURSOR_LOAD };
55 enum { CURSOR_DEFAULT = 0, CURSOR_HIDE = 1, CURSOR_WRAPNEXT = 2 };
56 enum { GLYPH_SET=1, GLYPH_DIRTY=2 };
57 enum { MODE_WRAP=1, MODE_INSERT=2, MODE_APPKEYPAD=4, MODE_ALTSCREEN=8 };
58 enum { ESC_START=1, ESC_CSI=2, ESC_OSC=4, ESC_TITLE=8, ESC_ALTCHARSET=16 };
59 enum { SCREEN_UPDATE, SCREEN_REDRAW };
60
61 typedef struct {
62         char c;     /* character code  */
63         char mode;  /* attribute flags */
64         int fg;     /* foreground      */
65         int bg;     /* background      */
66         char state; /* state flags     */
67 } Glyph;
68
69 typedef Glyph* Line;
70
71 typedef struct {
72         Glyph attr;      /* current char attributes */
73         int x;
74         int y;
75         char state;
76 } TCursor;
77
78 /* CSI Escape sequence structs */
79 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
80 typedef struct {
81         char buf[ESC_BUF_SIZ]; /* raw string */
82         int len;                           /* raw string length */
83         char priv;
84         int arg[ESC_ARG_SIZ];
85         int narg;                          /* nb of args */
86         char mode;
87 } CSIEscape;
88
89 /* Internal representation of the screen */
90 typedef struct {
91         int row;        /* nb row */  
92         int col;        /* nb col */
93         Line* line;     /* screen */
94         Line* alt;      /* alternate screen */
95         TCursor c;      /* cursor */
96         int top;        /* top    scroll limit */
97         int bot;        /* bottom scroll limit */
98         int mode;       /* terminal mode flags */
99         int esc;        /* escape state flags */
100         char title[ESC_TITLE_SIZ];
101         int titlelen;
102 } Term;
103
104 /* Purely graphic info */
105 typedef struct {
106         Display* dis;
107         Colormap cmap;
108         Window win;
109         Pixmap buf;
110         XIM xim;
111         XIC xic;
112         int scr;
113         int w;  /* window width  */
114         int h;  /* window height */
115         int bufw; /* pixmap width  */
116         int bufh; /* pixmap height */
117         int ch; /* char height */
118         int cw; /* char width  */
119         int focus;
120         int vis; /* is visible */
121 } XWindow; 
122
123 typedef struct {
124         KeySym k;
125         char s[ESC_BUF_SIZ];
126 } Key;
127
128 /* Drawing Context */
129 typedef struct {
130         unsigned long col[256];
131         XFontStruct* font;
132         XFontStruct* bfont;
133         GC gc;
134 } DC;
135
136 /* TODO: use better name for vars... */
137 typedef struct {
138         int mode;
139         int bx, by;
140         int ex, ey;
141         struct {int x, y;}  b, e;
142         char *clip;
143 } Selection;
144
145 #include "config.h"
146
147 static void die(const char *errstr, ...);
148 static void draw(int);
149 static void execsh(void);
150 static void sigchld(int);
151 static void run(void);
152
153 static void csidump(void);
154 static void csihandle(void);
155 static void csiparse(void);
156 static void csireset(void);
157
158 static void tclearregion(int, int, int, int);
159 static void tcursor(int);
160 static void tdeletechar(int);
161 static void tdeleteline(int);
162 static void tinsertblank(int);
163 static void tinsertblankline(int);
164 static void tmoveto(int, int);
165 static void tnew(int, int);
166 static void tnewline(void);
167 static void tputtab(void);
168 static void tputc(char);
169 static void tputs(char*, int);
170 static void treset(void);
171 static void tresize(int, int);
172 static void tscrollup(int, int);
173 static void tscrolldown(int, int);
174 static void tsetattr(int*, int);
175 static void tsetchar(char);
176 static void tsetscroll(int, int);
177 static void tswapscreen(void);
178
179 static void ttynew(void);
180 static void ttyread(void);
181 static void ttyresize(int, int);
182 static void ttywrite(const char *, size_t);
183
184 static void xdraws(char *, Glyph, int, int, int);
185 static void xhints(void);
186 static void xclear(int, int, int, int);
187 static void xdrawcursor(void);
188 static void xinit(void);
189 static void xloadcols(void);
190 static void xseturgency(int);
191
192 static void expose(XEvent *);
193 static void visibility(XEvent *);
194 static void unmap(XEvent *);
195 static char* kmap(KeySym);
196 static void kpress(XEvent *);
197 static void resize(XEvent *);
198 static void focus(XEvent *);
199 static void brelease(XEvent *);
200 static void bpress(XEvent *);
201 static void bmotion(XEvent *);
202
203
204 static void (*handler[LASTEvent])(XEvent *) = {
205         [KeyPress] = kpress,
206         [ConfigureNotify] = resize,
207         [VisibilityNotify] = visibility,
208         [UnmapNotify] = unmap,
209         [Expose] = expose,
210         [FocusIn] = focus,
211         [FocusOut] = focus,
212         [MotionNotify] = bmotion,
213         [ButtonPress] = bpress,
214         [ButtonRelease] = brelease,
215 };
216
217 /* Globals */
218 static DC dc;
219 static XWindow xw;
220 static Term term;
221 static CSIEscape escseq;
222 static int cmdfd;
223 static pid_t pid;
224 static Selection sel;
225 static char *opt_cmd   = NULL;
226 static char *opt_title = NULL;
227
228 void
229 selinit(void) {
230         sel.mode = 0;
231         sel.bx = -1;
232         sel.clip = NULL;
233 }
234
235 static inline int selected(int x, int y) {
236         if(sel.ey == y && sel.by == y) {
237                 int bx = MIN(sel.bx, sel.ex);
238                 int ex = MAX(sel.bx, sel.ex);
239                 return BETWEEN(x, bx, ex);
240         }
241         return ((sel.b.y < y&&y < sel.e.y) || (y==sel.e.y && x<=sel.e.x)) 
242                 || (y==sel.b.y && x>=sel.b.x && (x<=sel.e.x || sel.b.y!=sel.e.y));
243 }
244
245 static void getbuttoninfo(XEvent *e, int *b, int *x, int *y) {
246         if(b) *b = e->xbutton.state,
247                 *b=*b==4096?5:*b==2048?4:*b==1024?3:*b==512?2:*b==256?1:-1;
248         *x = e->xbutton.x/xw.cw;
249         *y = e->xbutton.y/xw.ch;
250         sel.b.x = sel.by < sel.ey ? sel.bx : sel.ex;
251         sel.b.y = MIN(sel.by, sel.ey);
252         sel.e.x = sel.by < sel.ey ? sel.ex : sel.bx;
253         sel.e.y = MAX(sel.by, sel.ey);
254 }
255
256 static void bpress(XEvent *e) {
257         sel.mode = 1;
258         sel.ex = sel.bx = e->xbutton.x/xw.cw;
259         sel.ey = sel.by = e->xbutton.y/xw.ch;
260 }
261
262 static char *getseltext() {
263         char *str, *ptr;
264         int ls, x, y, sz;
265         if(sel.bx == -1)
266                 return NULL;
267         sz = (term.col+1) * (sel.e.y-sel.b.y+1);
268         ptr = str = malloc(sz);
269         for(y = 0; y < term.row; y++) {
270                 for(x = 0; x < term.col; x++)
271                         if(term.line[y][x].state & GLYPH_SET && (ls = selected(x, y)))
272                                 *ptr = term.line[y][x].c, ptr++;
273                 if(ls)
274                         *ptr = '\n', ptr++;
275         }
276         *ptr = 0;
277         return str;
278 }
279
280 /* TODO: use X11 clipboard */
281 static void selcopy(char *str) {
282         free(sel.clip);
283         sel.clip = str;
284 }
285
286 static void selpaste() {
287         if(sel.clip)
288                 ttywrite(sel.clip, strlen(sel.clip));
289 }
290
291 /* TODO: doubleclick to select word */
292 static void brelease(XEvent *e) {
293         int b;
294         sel.mode = 0;
295         getbuttoninfo(e, &b, &sel.ex, &sel.ey);
296         if(sel.bx==sel.ex && sel.by==sel.ey) {
297                 sel.bx = -1;
298                 if(b==2)
299                         selpaste();
300         } else {
301                 if(b==1)
302                         selcopy(getseltext());
303         }
304         draw(1);
305 }
306
307 static void bmotion(XEvent *e) {
308         if (sel.mode) {
309                 getbuttoninfo(e, NULL, &sel.ex, &sel.ey);
310                 draw(1);
311         }
312 }
313
314 #ifdef DEBUG
315 void
316 tdump(void) {
317         int row, col;
318         Glyph c;
319
320         for(row = 0; row < term.row; row++) {
321                 for(col = 0; col < term.col; col++) {
322                         if(col == term.c.x && row == term.c.y)
323                                 putchar('#');
324                         else {
325                                 c = term.line[row][col];
326                                 putchar(c.state & GLYPH_SET ? c.c : '.');
327                         }
328                 }
329                 putchar('\n');
330         }
331 }
332 #endif
333
334 void
335 die(const char *errstr, ...) {
336         va_list ap;
337
338         va_start(ap, errstr);
339         vfprintf(stderr, errstr, ap);
340         va_end(ap);
341         exit(EXIT_FAILURE);
342 }
343
344 void
345 execsh(void) {
346         char *args[] = {getenv("SHELL"), "-i", NULL};
347         if(opt_cmd)
348                 args[0] = opt_cmd, args[1] = NULL;
349         else
350                 DEFAULT(args[0], SHELL);
351         putenv("TERM="TNAME);
352         execvp(args[0], args);
353 }
354
355 void 
356 sigchld(int a) {
357         int stat = 0;
358         if(waitpid(pid, &stat, 0) < 0)
359                 die("Waiting for pid %hd failed: %s\n", pid, SERRNO);
360         if(WIFEXITED(stat))
361                 exit(WEXITSTATUS(stat));
362         else
363                 exit(EXIT_FAILURE);
364 }
365
366 void
367 ttynew(void) {
368         int m, s;
369         
370         /* seems to work fine on linux, openbsd and freebsd */
371         struct winsize w = {term.row, term.col, 0, 0};
372         if(openpty(&m, &s, NULL, NULL, &w) < 0)
373                 die("openpty failed: %s\n", SERRNO);
374
375         switch(pid = fork()) {
376         case -1:
377                 die("fork failed\n");
378                 break;
379         case 0:
380                 setsid(); /* create a new process group */
381                 dup2(s, STDIN_FILENO);
382                 dup2(s, STDOUT_FILENO);
383                 dup2(s, STDERR_FILENO);
384                 if(ioctl(s, TIOCSCTTY, NULL) < 0)
385                         die("ioctl TIOCSCTTY failed: %s\n", SERRNO);
386                 close(s);
387                 close(m);
388                 execsh();
389                 break;
390         default:
391                 close(s);
392                 cmdfd = m;
393                 signal(SIGCHLD, sigchld);
394         }
395 }
396
397 void
398 dump(char c) {
399         static int col;
400         fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
401         if(++col % 10 == 0)
402                 fprintf(stderr, "\n");
403 }
404
405 void
406 ttyread(void) {
407         char buf[BUFSIZ];
408         int ret;
409
410         if((ret = read(cmdfd, buf, LEN(buf))) < 0)
411                 die("Couldn't read from shell: %s\n", SERRNO);
412         else
413                 tputs(buf, ret);
414 }
415
416 void
417 ttywrite(const char *s, size_t n) {
418         if(write(cmdfd, s, n) == -1)
419                 die("write error on tty: %s\n", SERRNO);
420 }
421
422 void
423 ttyresize(int x, int y) {
424         struct winsize w;
425
426         w.ws_row = term.row;
427         w.ws_col = term.col;
428         w.ws_xpixel = w.ws_ypixel = 0;
429         if(ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
430                 fprintf(stderr, "Couldn't set window size: %s\n", SERRNO);
431 }
432
433 void
434 tcursor(int mode) {
435         static TCursor c;
436
437         if(mode == CURSOR_SAVE)
438                 c = term.c;
439         else if(mode == CURSOR_LOAD)
440                 term.c = c, tmoveto(c.x, c.y);
441 }
442
443 void
444 treset(void) {
445         term.c = (TCursor){{
446                 .mode = ATTR_NULL, 
447                 .fg = DefaultFG, 
448                 .bg = DefaultBG
449         }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
450         
451         term.top = 0, term.bot = term.row - 1;
452         term.mode = MODE_WRAP;
453         tclearregion(0, 0, term.col-1, term.row-1);
454 }
455
456 void
457 tnew(int col, int row) {
458         /* set screen size */
459         term.row = row, term.col = col;
460         term.line = malloc(term.row * sizeof(Line));
461         term.alt  = malloc(term.row * sizeof(Line));
462         for(row = 0 ; row < term.row; row++) {
463                 term.line[row] = malloc(term.col * sizeof(Glyph));
464                 term.alt [row] = malloc(term.col * sizeof(Glyph));
465         }
466         /* setup screen */
467         treset();
468 }
469
470 void
471 tswapscreen(void) {
472         Line* tmp = term.line;
473         term.line = term.alt;
474         term.alt = tmp;
475         term.mode ^= MODE_ALTSCREEN;
476 }
477
478 void
479 tscrolldown(int orig, int n) {
480         int i;
481         Line temp;
482         
483         LIMIT(n, 0, term.bot-orig+1);
484
485         tclearregion(0, term.bot-n+1, term.col-1, term.bot);
486         
487         for(i = term.bot; i >= orig+n; i--) {
488                 temp = term.line[i];
489                 term.line[i] = term.line[i-n];
490                 term.line[i-n] = temp;
491         }
492 }
493
494 void
495 tscrollup(int orig, int n) {
496         int i;
497         Line temp;
498         LIMIT(n, 0, term.bot-orig+1);
499         
500         tclearregion(0, orig, term.col-1, orig+n-1);
501         
502         for(i = orig; i <= term.bot-n; i++) { 
503                  temp = term.line[i];
504                  term.line[i] = term.line[i+n]; 
505                  term.line[i+n] = temp;
506         }
507 }
508
509 void
510 tnewline(void) {
511         int y = term.c.y;
512         if(term.c.y == term.bot)
513                 tscrollup(term.top, 1);
514         else
515                 y++;
516         tmoveto(0, y);
517 }
518
519 void
520 csiparse(void) {
521         /* int noarg = 1; */
522         char *p = escseq.buf;
523
524         escseq.narg = 0;
525         if(*p == '?')
526                 escseq.priv = 1, p++;
527         
528         while(p < escseq.buf+escseq.len) {
529                 while(isdigit(*p)) {
530                         escseq.arg[escseq.narg] *= 10;
531                         escseq.arg[escseq.narg] += *p++ - '0'/*, noarg = 0 */;
532                 }
533                 if(*p == ';' && escseq.narg+1 < ESC_ARG_SIZ)
534                         escseq.narg++, p++;
535                 else {
536                         escseq.mode = *p;
537                         escseq.narg++;
538                         return;
539                 }
540         }
541 }
542
543 void
544 tmoveto(int x, int y) {
545         LIMIT(x, 0, term.col-1);
546         LIMIT(y, 0, term.row-1);
547         term.c.state &= ~CURSOR_WRAPNEXT;
548         term.c.x = x;
549         term.c.y = y;
550 }
551
552 void
553 tsetchar(char c) {
554         term.line[term.c.y][term.c.x] = term.c.attr;
555         term.line[term.c.y][term.c.x].c = c;
556         term.line[term.c.y][term.c.x].state |= GLYPH_SET;
557 }
558
559 void
560 tclearregion(int x1, int y1, int x2, int y2) {
561         int y, temp;
562
563         if(x1 > x2)
564                 temp = x1, x1 = x2, x2 = temp;
565         if(y1 > y2)
566                 temp = y1, y1 = y2, y2 = temp;
567
568         LIMIT(x1, 0, term.col-1);
569         LIMIT(x2, 0, term.col-1);
570         LIMIT(y1, 0, term.row-1);
571         LIMIT(y2, 0, term.row-1);
572
573         for(y = y1; y <= y2; y++)
574                 memset(&term.line[y][x1], 0, sizeof(Glyph)*(x2-x1+1));
575 }
576
577 void
578 tdeletechar(int n) {
579         int src = term.c.x + n;
580         int dst = term.c.x;
581         int size = term.col - src;
582
583         if(src >= term.col) {
584                 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
585                 return;
586         }
587         memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph));
588         tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
589 }
590
591 void
592 tinsertblank(int n) {
593         int src = term.c.x;
594         int dst = src + n;
595         int size = term.col - dst;
596
597         if(dst >= term.col) {
598                 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
599                 return;
600         }
601         memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph));
602         tclearregion(src, term.c.y, dst - 1, term.c.y);
603 }
604
605 void
606 tinsertblankline(int n) {
607         if(term.c.y < term.top || term.c.y > term.bot)
608                 return;
609
610         tscrolldown(term.c.y, n);
611 }
612
613 void
614 tdeleteline(int n) {
615         if(term.c.y < term.top || term.c.y > term.bot)
616                 return;
617
618         tscrollup(term.c.y, n);
619 }
620
621 void
622 tsetattr(int *attr, int l) {
623         int i;
624
625         for(i = 0; i < l; i++) {
626                 switch(attr[i]) {
627                 case 0:
628                         term.c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
629                         term.c.attr.fg = DefaultFG;
630                         term.c.attr.bg = DefaultBG;
631                         break;
632                 case 1:
633                         term.c.attr.mode |= ATTR_BOLD;   
634                         break;
635                 case 4: 
636                         term.c.attr.mode |= ATTR_UNDERLINE;
637                         break;
638                 case 7: 
639                         term.c.attr.mode |= ATTR_REVERSE;       
640                         break;
641                 case 22: 
642                         term.c.attr.mode &= ~ATTR_BOLD;  
643                         break;
644                 case 24: 
645                         term.c.attr.mode &= ~ATTR_UNDERLINE;
646                         break;
647                 case 27: 
648                         term.c.attr.mode &= ~ATTR_REVERSE;       
649                         break;
650                 case 38:
651                         if (i + 2 < l && attr[i + 1] == 5) {
652                                 i += 2;
653                                 if (BETWEEN(attr[i], 0, 255))
654                                         term.c.attr.fg = attr[i];
655                                 else
656                                         fprintf(stderr, "erresc: bad fgcolor %d\n", attr[i]);
657                         }
658                         else
659                                 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[i]); 
660                         break;
661                 case 39:
662                         term.c.attr.fg = DefaultFG;
663                         break;
664                 case 48:
665                         if (i + 2 < l && attr[i + 1] == 5) {
666                                 i += 2;
667                                 if (BETWEEN(attr[i], 0, 255))
668                                         term.c.attr.bg = attr[i];
669                                 else
670                                         fprintf(stderr, "erresc: bad bgcolor %d\n", attr[i]);
671                         }
672                         else
673                                 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[i]); 
674                         break;
675                 case 49:
676                         term.c.attr.bg = DefaultBG;
677                         break;
678                 default:
679                         if(BETWEEN(attr[i], 30, 37))
680                                 term.c.attr.fg = attr[i] - 30;
681                         else if(BETWEEN(attr[i], 40, 47))
682                                 term.c.attr.bg = attr[i] - 40;
683                         else if(BETWEEN(attr[i], 90, 97))
684                                 term.c.attr.fg = attr[i] - 90 + 8;
685                         else if(BETWEEN(attr[i], 100, 107))
686                                 term.c.attr.fg = attr[i] - 100 + 8;
687                         else 
688                                 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[i]), csidump();
689                         
690                         break;
691                 }
692         }
693 }
694
695 void
696 tsetscroll(int t, int b) {
697         int temp;
698
699         LIMIT(t, 0, term.row-1);
700         LIMIT(b, 0, term.row-1);
701         if(t > b) {
702                 temp = t;
703                 t = b;
704                 b = temp;
705         }
706         term.top = t;
707         term.bot = b;    
708 }
709
710 void
711 csihandle(void) {
712         switch(escseq.mode) {
713         default:
714         unknown:
715                 printf("erresc: unknown csi ");
716                 csidump();
717                 /* die(""); */
718                 break;
719         case '@': /* ICH -- Insert <n> blank char */
720                 DEFAULT(escseq.arg[0], 1);
721                 tinsertblank(escseq.arg[0]);
722                 break;
723         case 'A': /* CUU -- Cursor <n> Up */
724         case 'e':
725                 DEFAULT(escseq.arg[0], 1);
726                 tmoveto(term.c.x, term.c.y-escseq.arg[0]);
727                 break;
728         case 'B': /* CUD -- Cursor <n> Down */
729                 DEFAULT(escseq.arg[0], 1);
730                 tmoveto(term.c.x, term.c.y+escseq.arg[0]);
731                 break;
732         case 'C': /* CUF -- Cursor <n> Forward */
733         case 'a':
734                 DEFAULT(escseq.arg[0], 1);
735                 tmoveto(term.c.x+escseq.arg[0], term.c.y);
736                 break;
737         case 'D': /* CUB -- Cursor <n> Backward */
738                 DEFAULT(escseq.arg[0], 1);
739                 tmoveto(term.c.x-escseq.arg[0], term.c.y);
740                 break;
741         case 'E': /* CNL -- Cursor <n> Down and first col */
742                 DEFAULT(escseq.arg[0], 1);
743                 tmoveto(0, term.c.y+escseq.arg[0]);
744                 break;
745         case 'F': /* CPL -- Cursor <n> Up and first col */
746                 DEFAULT(escseq.arg[0], 1);
747                 tmoveto(0, term.c.y-escseq.arg[0]);
748                 break;
749         case 'G': /* CHA -- Move to <col> */
750         case '`': /* XXX: HPA -- same? */
751                 DEFAULT(escseq.arg[0], 1);
752                 tmoveto(escseq.arg[0]-1, term.c.y);
753                 break;
754         case 'H': /* CUP -- Move to <row> <col> */
755         case 'f': /* XXX: HVP -- same? */
756                 DEFAULT(escseq.arg[0], 1);
757                 DEFAULT(escseq.arg[1], 1);
758                 tmoveto(escseq.arg[1]-1, escseq.arg[0]-1);
759                 break;
760         /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
761         case 'J': /* ED -- Clear screen */
762                 switch(escseq.arg[0]) {
763                 case 0: /* below */
764                         tclearregion(term.c.x, term.c.y, term.col-1, term.row-1);
765                         break;
766                 case 1: /* above */
767                         tclearregion(0, 0, term.c.x, term.c.y);
768                         break;
769                 case 2: /* all */
770                         tclearregion(0, 0, term.col-1, term.row-1);
771                         break;
772                 default:
773                         goto unknown;
774                 }
775                 break;
776         case 'K': /* EL -- Clear line */
777                 switch(escseq.arg[0]) {
778                 case 0: /* right */
779                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
780                         break;
781                 case 1: /* left */
782                         tclearregion(0, term.c.y, term.c.x, term.c.y);
783                         break;
784                 case 2: /* all */
785                         tclearregion(0, term.c.y, term.col-1, term.c.y);
786                         break;
787                 }
788                 break;
789         case 'S': /* SU -- Scroll <n> line up */
790                 DEFAULT(escseq.arg[0], 1);
791                 tscrollup(term.top, escseq.arg[0]);
792                 break;
793         case 'T': /* SD -- Scroll <n> line down */
794                 DEFAULT(escseq.arg[0], 1);
795                 tscrolldown(term.top, escseq.arg[0]);
796                 break;
797         case 'L': /* IL -- Insert <n> blank lines */
798                 DEFAULT(escseq.arg[0], 1);
799                 tinsertblankline(escseq.arg[0]);
800                 break;
801         case 'l': /* RM -- Reset Mode */
802                 if(escseq.priv) {
803                         switch(escseq.arg[0]) {
804                         case 1:
805                                 term.mode &= ~MODE_APPKEYPAD;
806                                 break;
807                         case 5: /* TODO: DECSCNM -- Remove reverse video */
808                                 break;
809                         case 7:
810                                 term.mode &= ~MODE_WRAP;
811                                 break;
812                         case 12: /* att610 -- Stop blinking cursor (IGNORED) */
813                                 break;
814                         case 25:
815                                 term.c.state |= CURSOR_HIDE;
816                                 break;
817                         case 1049: /* = 1047 and 1048 */
818                         case 1047:
819                                 if(IS_SET(MODE_ALTSCREEN)) {
820                                         tclearregion(0, 0, term.col-1, term.row-1);
821                                         tswapscreen();
822                                 }
823                                 if(escseq.arg[0] == 1047)
824                                         break;
825                         case 1048:
826                                 tcursor(CURSOR_LOAD);
827                                 break;
828                         default:
829                                 goto unknown;
830                         }
831                 } else {
832                         switch(escseq.arg[0]) {
833                         case 4:
834                                 term.mode &= ~MODE_INSERT;
835                                 break;
836                         default:
837                                 goto unknown;
838                         }
839                 }
840                 break;
841         case 'M': /* DL -- Delete <n> lines */
842                 DEFAULT(escseq.arg[0], 1);
843                 tdeleteline(escseq.arg[0]);
844                 break;
845         case 'X': /* ECH -- Erase <n> char */
846                 DEFAULT(escseq.arg[0], 1);
847                 tclearregion(term.c.x, term.c.y, term.c.x + escseq.arg[0], term.c.y);
848                 break;
849         case 'P': /* DCH -- Delete <n> char */
850                 DEFAULT(escseq.arg[0], 1);
851                 tdeletechar(escseq.arg[0]);
852                 break;
853         /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
854         case 'd': /* VPA -- Move to <row> */
855                 DEFAULT(escseq.arg[0], 1);
856                 tmoveto(term.c.x, escseq.arg[0]-1);
857                 break;
858         case 'h': /* SM -- Set terminal mode */
859                 if(escseq.priv) {
860                         switch(escseq.arg[0]) {
861                         case 1:
862                                 term.mode |= MODE_APPKEYPAD;
863                                 break;
864                         case 5: /* DECSCNM -- Reverve video */
865                                 /* TODO: set REVERSE on the whole screen (f) */
866                                 break;
867                         case 7:
868                                 term.mode |= MODE_WRAP;
869                                 break;
870                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
871                                  /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
872                                 if(escseq.narg > 1 && escseq.arg[1] != 25)
873                                         break;
874                         case 25:
875                                 term.c.state &= ~CURSOR_HIDE;
876                                 break;
877                         case 1049: /* = 1047 and 1048 */
878                         case 1047:
879                                 if(IS_SET(MODE_ALTSCREEN))
880                                         tclearregion(0, 0, term.col-1, term.row-1);
881                                 else
882                                         tswapscreen();
883                                 if(escseq.arg[0] == 1047)
884                                         break;
885                         case 1048:
886                                 tcursor(CURSOR_SAVE);
887                                 break;
888                         default: goto unknown;
889                         }
890                 } else {
891                         switch(escseq.arg[0]) {
892                         case 4:
893                                 term.mode |= MODE_INSERT;
894                                 break;
895                         default: goto unknown;
896                         }
897                 };
898                 break;
899         case 'm': /* SGR -- Terminal attribute (color) */
900                 tsetattr(escseq.arg, escseq.narg);
901                 break;
902         case 'r': /* DECSTBM -- Set Scrolling Region */
903                 if(escseq.priv)
904                         goto unknown;
905                 else {
906                         DEFAULT(escseq.arg[0], 1);
907                         DEFAULT(escseq.arg[1], term.row);
908                         tsetscroll(escseq.arg[0]-1, escseq.arg[1]-1);
909                         tmoveto(0, 0);
910                 }
911                 break;
912         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
913                 tcursor(CURSOR_SAVE);
914                 break;
915         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
916                 tcursor(CURSOR_LOAD);
917                 break;
918         }
919 }
920
921 void
922 csidump(void) { 
923         int i;
924         printf("ESC [ %s", escseq.priv ? "? " : "");
925         if(escseq.narg)
926                 for(i = 0; i < escseq.narg; i++)
927                         printf("%d ", escseq.arg[i]);
928         if(escseq.mode)
929                 putchar(escseq.mode);
930         putchar('\n');
931 }
932
933 void
934 csireset(void) {
935         memset(&escseq, 0, sizeof(escseq));
936 }
937
938 void
939 tputtab(void) {
940         int space = TAB - term.c.x % TAB;
941         tmoveto(term.c.x + space, term.c.y);
942 }
943
944 void
945 tputc(char c) {
946         if(term.esc & ESC_START) {
947                 if(term.esc & ESC_CSI) {
948                         escseq.buf[escseq.len++] = c;
949                         if(BETWEEN(c, 0x40, 0x7E) || escseq.len >= ESC_BUF_SIZ) {
950                                 term.esc = 0;
951                                 csiparse(), csihandle();
952                         }
953                         /* TODO: handle other OSC */
954                 } else if(term.esc & ESC_OSC) { 
955                         if(c == ';') {
956                                 term.titlelen = 0;
957                                 term.esc = ESC_START | ESC_TITLE;
958                         }
959                 } else if(term.esc & ESC_TITLE) {
960                         if(c == '\a' || term.titlelen+1 >= ESC_TITLE_SIZ) {
961                                 term.esc = 0;
962                                 term.title[term.titlelen] = '\0';
963                                 XStoreName(xw.dis, xw.win, term.title);
964                         } else {
965                                 term.title[term.titlelen++] = c;
966                         }
967                 } else if(term.esc & ESC_ALTCHARSET) {
968                         switch(c) {
969                         case '0': /* Line drawing crap */
970                                 term.c.attr.mode |= ATTR_GFX;
971                                 break;
972                         case 'B': /* Back to regular text */
973                                 term.c.attr.mode &= ~ATTR_GFX;
974                                 break;
975                         default:
976                                 printf("esc unhandled charset: ESC ( %c\n", c);
977                         }
978                         term.esc = 0;
979                 } else {
980                         switch(c) {
981                         case '[':
982                                 term.esc |= ESC_CSI;
983                                 break;
984                         case ']':
985                                 term.esc |= ESC_OSC;
986                                 break;
987                         case '(':
988                                 term.esc |= ESC_ALTCHARSET;
989                                 break;
990                         case 'D': /* IND -- Linefeed */
991                                 if(term.c.y == term.bot)
992                                         tscrollup(term.top, 1);
993                                 else
994                                         tmoveto(term.c.x, term.c.y+1);
995                                 term.esc = 0;
996                                 break;
997                         case 'E': /* NEL -- Next line */
998                                 tnewline();
999                                 term.esc = 0;
1000                                 break;
1001                         case 'M': /* RI -- Reverse index */
1002                                 if(term.c.y == term.top)
1003                                         tscrolldown(term.top, 1);
1004                                 else
1005                                         tmoveto(term.c.x, term.c.y-1);
1006                                 term.esc = 0;
1007                                 break;
1008                         case 'c': /* RIS -- Reset to inital state */
1009                                 treset();
1010                                 term.esc = 0;
1011                                 break;
1012                         case '=': /* DECPAM -- Application keypad */
1013                                 term.mode |= MODE_APPKEYPAD;
1014                                 term.esc = 0;
1015                                 break;
1016                         case '>': /* DECPNM -- Normal keypad */
1017                                 term.mode &= ~MODE_APPKEYPAD;
1018                                 term.esc = 0;
1019                                 break;
1020                         case '7': /* DECSC -- Save Cursor */
1021                                 tcursor(CURSOR_SAVE);
1022                                 term.esc = 0;
1023                                 break;
1024                         case '8': /* DECRC -- Restore Cursor */
1025                                 tcursor(CURSOR_LOAD);
1026                                 term.esc = 0;
1027                                 break;
1028                         default:
1029                                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", c, isprint(c)?c:'.');
1030                                 term.esc = 0;
1031                         }
1032                 }
1033         } else {
1034                 switch(c) {
1035                 case '\t':
1036                         tputtab();
1037                         break;
1038                 case '\b':
1039                         tmoveto(term.c.x-1, term.c.y);
1040                         break;
1041                 case '\r':
1042                         tmoveto(0, term.c.y);
1043                         break;
1044                 case '\n':
1045                         tnewline();
1046                         break;
1047                 case '\a':
1048                         if(!xw.focus)
1049                                 xseturgency(1);
1050                         break;
1051                 case '\033':
1052                         csireset();
1053                         term.esc = ESC_START;
1054                         break;
1055                 default:
1056                         if(IS_SET(MODE_WRAP) && term.c.state & CURSOR_WRAPNEXT)
1057                                 tnewline();
1058                         tsetchar(c);
1059                         if(term.c.x+1 < term.col)
1060                                 tmoveto(term.c.x+1, term.c.y);
1061                         else
1062                                 term.c.state |= CURSOR_WRAPNEXT;
1063                         break;
1064                 }
1065         }
1066 }
1067
1068 void
1069 tputs(char *s, int len) {
1070         for(; len > 0; len--)
1071                 tputc(*s++);
1072 }
1073
1074 void
1075 tresize(int col, int row) {
1076         int i;
1077         int minrow = MIN(row, term.row);
1078         int mincol = MIN(col, term.col);
1079         int slide = term.c.y - row + 1;
1080
1081         if(col < 1 || row < 1)
1082                 return;
1083
1084         /* free unneeded rows */
1085         i = 0;
1086         if(slide > 0) {
1087                 /* slide screen to keep cursor where we expect it -
1088                  * tscrollup would work here, but we can optimize to
1089                  * memmove because we're freeing the earlier lines */
1090                 for(/* i = 0 */; i < slide; i++) {
1091                         free(term.line[i]);
1092                         free(term.alt[i]);
1093                 }
1094                 memmove(term.line, term.line + slide, row * sizeof(Line));
1095                 memmove(term.alt, term.alt + slide, row * sizeof(Line));
1096         }
1097         for(i += row; i < term.row; i++) {
1098                 free(term.line[i]);
1099                 free(term.alt[i]);
1100         }
1101
1102         /* resize to new height */
1103         term.line = realloc(term.line, row * sizeof(Line));
1104         term.alt  = realloc(term.alt,  row * sizeof(Line));
1105
1106         /* resize each row to new width, zero-pad if needed */
1107         for(i = 0; i < minrow; i++) {
1108                 term.line[i] = realloc(term.line[i], col * sizeof(Glyph));
1109                 term.alt[i]  = realloc(term.alt[i],  col * sizeof(Glyph));
1110                 memset(term.line[i] + mincol, 0, (col - mincol) * sizeof(Glyph));
1111                 memset(term.alt[i]  + mincol, 0, (col - mincol) * sizeof(Glyph));
1112         }
1113
1114         /* allocate any new rows */
1115         for(/* i == minrow */; i < row; i++) {
1116                 term.line[i] = calloc(col, sizeof(Glyph));
1117                 term.alt [i] = calloc(col, sizeof(Glyph));
1118         }
1119         
1120         /* update terminal size */
1121         term.col = col, term.row = row;
1122         /* make use of the LIMIT in tmoveto */
1123         tmoveto(term.c.x, term.c.y);
1124         /* reset scrolling region */
1125         tsetscroll(0, row-1);
1126 }
1127
1128 void
1129 xloadcols(void) {
1130         int i, r, g, b;
1131         XColor color;
1132         unsigned long white = WhitePixel(xw.dis, xw.scr);
1133
1134         for(i = 0; i < 16; i++) {
1135                 if (!XAllocNamedColor(xw.dis, xw.cmap, colorname[i], &color, &color)) {
1136                         dc.col[i] = white;
1137                         fprintf(stderr, "Could not allocate color '%s'\n", colorname[i]);
1138                 } else
1139                         dc.col[i] = color.pixel;
1140         }
1141
1142         /* same colors as xterm */
1143         for(r = 0; r < 6; r++)
1144                 for(g = 0; g < 6; g++)
1145                         for(b = 0; b < 6; b++) {
1146                                 color.red = r == 0 ? 0 : 0x3737 + 0x2828 * r;
1147                                 color.green = g == 0 ? 0 : 0x3737 + 0x2828 * g;
1148                                 color.blue = b == 0 ? 0 : 0x3737 + 0x2828 * b;
1149                                 if (!XAllocColor(xw.dis, xw.cmap, &color)) {
1150                                         dc.col[i] = white;
1151                                         fprintf(stderr, "Could not allocate color %d\n", i);
1152                                 } else
1153                                         dc.col[i] = color.pixel;
1154                                 i++;
1155                         }
1156
1157         for(r = 0; r < 24; r++, i++) {
1158                 color.red = color.green = color.blue = 0x0808 + 0x0a0a * r;
1159                 if (!XAllocColor(xw.dis, xw.cmap, &color)) {
1160                         dc.col[i] = white;
1161                         fprintf(stderr, "Could not allocate color %d\n", i);
1162                 } else
1163                         dc.col[i] = color.pixel;
1164         }
1165 }
1166
1167 void
1168 xclear(int x1, int y1, int x2, int y2) {
1169         XSetForeground(xw.dis, dc.gc, dc.col[DefaultBG]);
1170         XFillRectangle(xw.dis, xw.buf, dc.gc,
1171                        x1 * xw.cw, y1 * xw.ch,
1172                        (x2-x1+1) * xw.cw, (y2-y1+1) * xw.ch);
1173 }
1174
1175 void
1176 xhints(void)
1177 {
1178         XClassHint class = {TNAME, TNAME};
1179         XWMHints wm = {.flags = InputHint, .input = 1};
1180         XSizeHints size = {
1181                 .flags = PSize | PResizeInc | PBaseSize,
1182                 .height = xw.h,
1183                 .width = xw.w,
1184                 .height_inc = xw.ch,
1185                 .width_inc = xw.cw,
1186                 .base_height = 2*BORDER,
1187                 .base_width = 2*BORDER,
1188         };
1189         XSetWMProperties(xw.dis, xw.win, NULL, NULL, NULL, 0, &size, &wm, &class);
1190 }
1191
1192 void
1193 xinit(void) {
1194         XSetWindowAttributes attrs;
1195
1196         if(!(xw.dis = XOpenDisplay(NULL)))
1197                 die("Can't open display\n");
1198         xw.scr = XDefaultScreen(xw.dis);
1199         
1200         /* font */
1201         if(!(dc.font = XLoadQueryFont(xw.dis, FONT)) || !(dc.bfont = XLoadQueryFont(xw.dis, BOLDFONT)))
1202                 die("Can't load font %s\n", dc.font ? BOLDFONT : FONT);
1203
1204         /* XXX: Assuming same size for bold font */
1205         xw.cw = dc.font->max_bounds.rbearing - dc.font->min_bounds.lbearing;
1206         xw.ch = dc.font->ascent + dc.font->descent;
1207
1208         /* colors */
1209         xw.cmap = XDefaultColormap(xw.dis, xw.scr);
1210         xloadcols();
1211
1212         /* window - default size */
1213         xw.bufh = 24 * xw.ch;
1214         xw.bufw = 80 * xw.cw;
1215         xw.h = xw.bufh + 2*BORDER;
1216         xw.w = xw.bufw + 2*BORDER;
1217
1218         attrs.background_pixel = dc.col[DefaultBG];
1219         attrs.border_pixel = dc.col[DefaultBG];
1220         attrs.bit_gravity = NorthWestGravity;
1221         attrs.event_mask = FocusChangeMask | KeyPressMask
1222                 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
1223                 | PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
1224         attrs.colormap = xw.cmap;
1225
1226         xw.win = XCreateWindow(xw.dis, XRootWindow(xw.dis, xw.scr), 0, 0,
1227                         xw.w, xw.h, 0, XDefaultDepth(xw.dis, xw.scr), InputOutput,
1228                         XDefaultVisual(xw.dis, xw.scr),
1229                         CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask
1230                         | CWColormap,
1231                         &attrs);
1232         xw.buf = XCreatePixmap(xw.dis, xw.win, xw.bufw, xw.bufh, XDefaultDepth(xw.dis, xw.scr));
1233
1234
1235         /* input methods */
1236         xw.xim = XOpenIM(xw.dis, NULL, NULL, NULL);
1237         xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing 
1238                                            | XIMStatusNothing, XNClientWindow, xw.win, 
1239                                            XNFocusWindow, xw.win, NULL);
1240         /* gc */
1241         dc.gc = XCreateGC(xw.dis, xw.win, 0, NULL);
1242         
1243         XMapWindow(xw.dis, xw.win);
1244         xhints();
1245         XStoreName(xw.dis, xw.win, opt_title ? opt_title : "st");
1246         XSync(xw.dis, 0);
1247 }
1248
1249 void
1250 xdraws(char *s, Glyph base, int x, int y, int len) {
1251         unsigned long xfg, xbg;
1252         int winx = x*xw.cw, winy = y*xw.ch + dc.font->ascent, width = len*xw.cw;
1253         int i;
1254
1255         if(base.mode & ATTR_REVERSE)
1256                 xfg = dc.col[base.bg], xbg = dc.col[base.fg];
1257         else
1258                 xfg = dc.col[base.fg], xbg = dc.col[base.bg];
1259
1260         XSetBackground(xw.dis, dc.gc, xbg);
1261         XSetForeground(xw.dis, dc.gc, xfg);
1262         
1263         if(base.mode & ATTR_GFX)
1264                 for(i = 0; i < len; i++) {
1265                         char c = gfx[(unsigned int)s[i] % 256];
1266                         if(c)
1267                                 s[i] = c;
1268                         else if(s[i] > 0x5f)
1269                                 s[i] -= 0x5f;
1270                 }
1271
1272         XSetFont(xw.dis, dc.gc, base.mode & ATTR_BOLD ? dc.bfont->fid : dc.font->fid);
1273         XDrawImageString(xw.dis, xw.buf, dc.gc, winx, winy, s, len);
1274         
1275         if(base.mode & ATTR_UNDERLINE)
1276                 XDrawLine(xw.dis, xw.buf, dc.gc, winx, winy+1, winx+width-1, winy+1);
1277 }
1278
1279 void
1280 xdrawcursor(void) {
1281         static int oldx = 0;
1282         static int oldy = 0;
1283         Glyph g = {' ', ATTR_NULL, DefaultBG, DefaultCS, 0};
1284         
1285         LIMIT(oldx, 0, term.col-1);
1286         LIMIT(oldy, 0, term.row-1);
1287         
1288         if(term.line[term.c.y][term.c.x].state & GLYPH_SET)
1289                 g.c = term.line[term.c.y][term.c.x].c;
1290         
1291         /* remove the old cursor */
1292         if(term.line[oldy][oldx].state & GLYPH_SET)
1293                 xdraws(&term.line[oldy][oldx].c, term.line[oldy][oldx], oldx, oldy, 1);
1294         else
1295                 xclear(oldx, oldy, oldx, oldy);
1296         
1297         /* draw the new one */
1298         if(!(term.c.state & CURSOR_HIDE) && xw.focus) {
1299                 xdraws(&g.c, g, term.c.x, term.c.y, 1);
1300                 oldx = term.c.x, oldy = term.c.y;
1301         }
1302 }
1303
1304 #ifdef DEBUG
1305 /* basic drawing routines */
1306 void
1307 xdrawc(int x, int y, Glyph g) {
1308         XRectangle r = { x * xw.cw, y * xw.ch, xw.cw, xw.ch };
1309         XSetBackground(xw.dis, dc.gc, dc.col[g.bg]);
1310         XSetForeground(xw.dis, dc.gc, dc.col[g.fg]);
1311         XSetFont(xw.dis, dc.gc, g.mode & ATTR_BOLD ? dc.bfont->fid : dc.font->fid);
1312         XDrawImageString(xw.dis, xw.buf, dc.gc, r.x, r.y+dc.font->ascent, &g.c, 1);
1313 }
1314
1315 void
1316 draw(int dummy) {
1317         int x, y;
1318
1319         xclear(0, 0, term.col-1, term.row-1);
1320         for(y = 0; y < term.row; y++)
1321                 for(x = 0; x < term.col; x++)
1322                         if(term.line[y][x].state & GLYPH_SET)
1323                                 xdrawc(x, y, term.line[y][x]);
1324
1325         xdrawcursor();
1326         XCopyArea(xw.dis, xw.buf, xw.win, dc.gc, 0, 0, xw.bufw, xw.bufh, BORDER, BORDER);
1327         XFlush(xw.dis);
1328 }
1329
1330 #else
1331 /* optimized drawing routine */
1332 void
1333 draw(int redraw_all) {
1334         int i, x, y, ox;
1335         Glyph base, new;
1336         char buf[DRAW_BUF_SIZ];
1337
1338         if(!xw.vis)
1339                 return;
1340
1341         xclear(0, 0, term.col-1, term.row-1);
1342         for(y = 0; y < term.row; y++) {
1343                 base = term.line[y][0];
1344                 i = ox = 0;
1345                 for(x = 0; x < term.col; x++) {
1346                         new = term.line[y][x];
1347                         if(sel.bx!=-1 && new.c && selected(x, y))
1348                                 new.mode ^= ATTR_REVERSE;
1349                         if(i > 0 && (!(new.state & GLYPH_SET) || ATTRCMP(base, new) ||
1350                                         i >= DRAW_BUF_SIZ)) {
1351                                 xdraws(buf, base, ox, y, i);
1352                                 i = 0;
1353                         }
1354                         if(new.state & GLYPH_SET) {
1355                                 if(i == 0) {
1356                                         ox = x;
1357                                         base = new;
1358                                 }
1359                                 buf[i++] = new.c;
1360                         }
1361                 }
1362                 if(i > 0)
1363                         xdraws(buf, base, ox, y, i);
1364         }
1365         xdrawcursor();
1366         XCopyArea(xw.dis, xw.buf, xw.win, dc.gc, 0, 0, xw.bufw, xw.bufh, BORDER, BORDER);
1367         XFlush(xw.dis);
1368 }
1369
1370 #endif
1371
1372 void
1373 expose(XEvent *ev) {
1374         draw(SCREEN_REDRAW);
1375 }
1376
1377 void
1378 visibility(XEvent *ev) {
1379         XVisibilityEvent *e = &ev->xvisibility;
1380         /* XXX if this goes from 0 to 1, need a full redraw for next Expose,
1381          * not just a buf copy */
1382         xw.vis = e->state != VisibilityFullyObscured;
1383 }
1384
1385 void
1386 unmap(XEvent *ev) {
1387         xw.vis = 0;
1388 }
1389
1390 void
1391 xseturgency(int add) {
1392         XWMHints *h = XGetWMHints(xw.dis, xw.win);
1393         h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
1394         XSetWMHints(xw.dis, xw.win, h);
1395         XFree(h);
1396 }
1397
1398 void
1399 focus(XEvent *ev) {
1400         if((xw.focus = ev->type == FocusIn))
1401                 xseturgency(0);
1402         draw(SCREEN_UPDATE);
1403 }
1404
1405 char*
1406 kmap(KeySym k) {
1407         int i;
1408         for(i = 0; i < LEN(key); i++)
1409                 if(key[i].k == k)
1410                         return (char*)key[i].s;
1411         return NULL;
1412 }
1413
1414 void
1415 kpress(XEvent *ev) {
1416         XKeyEvent *e = &ev->xkey;
1417         KeySym ksym;
1418         char buf[32];
1419         char *customkey;
1420         int len;
1421         int meta;
1422         int shift;
1423         Status status;
1424
1425         meta = e->state & Mod1Mask;
1426         shift = e->state & ShiftMask;
1427         len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
1428
1429         if((customkey = kmap(ksym)))
1430                 ttywrite(customkey, strlen(customkey));
1431         else if(len > 0) {
1432                 buf[sizeof(buf)-1] = '\0';
1433                 if(meta && len == 1)
1434                         ttywrite("\033", 1);
1435                 ttywrite(buf, len);
1436         } else
1437                 switch(ksym) {
1438                 case XK_Up:
1439                 case XK_Down:
1440                 case XK_Left:
1441                 case XK_Right:
1442                         sprintf(buf, "\033%c%c", IS_SET(MODE_APPKEYPAD) ? 'O' : '[', "DACB"[ksym - XK_Left]);
1443                         ttywrite(buf, 3);
1444                         break;
1445                 case XK_Insert:
1446                         if(shift)
1447                                 selpaste(), draw(1);
1448                         break;
1449                 default:
1450                         fprintf(stderr, "errkey: %d\n", (int)ksym);
1451                         break;
1452                 }
1453 }
1454
1455 void
1456 resize(XEvent *e) {
1457         int col, row;
1458         
1459         if(e->xconfigure.width == xw.w && e->xconfigure.height == xw.h)
1460                 return;
1461         
1462         xw.w = e->xconfigure.width;
1463         xw.h = e->xconfigure.height;
1464         xw.bufw = xw.w - 2*BORDER;
1465         xw.bufh = xw.h - 2*BORDER;
1466         col = xw.bufw / xw.cw;
1467         row = xw.bufh / xw.ch;
1468         tresize(col, row);
1469         ttyresize(col, row);
1470         xw.bufh = MAX(1, xw.bufh);
1471         xw.bufw = MAX(1, xw.bufw);
1472         XFreePixmap(xw.dis, xw.buf);
1473         xw.buf = XCreatePixmap(xw.dis, xw.win, xw.bufw, xw.bufh, XDefaultDepth(xw.dis, xw.scr));
1474 }
1475
1476 void
1477 run(void) {
1478         XEvent ev;
1479         fd_set rfd;
1480         int xfd = XConnectionNumber(xw.dis);
1481
1482         for(;;) {
1483                 FD_ZERO(&rfd);
1484                 FD_SET(cmdfd, &rfd);
1485                 FD_SET(xfd, &rfd);
1486                 if(select(MAX(xfd, cmdfd)+1, &rfd, NULL, NULL, NULL) < 0) {
1487                         if(errno == EINTR)
1488                                 continue;
1489                         die("select failed: %s\n", SERRNO);
1490                 }
1491                 if(FD_ISSET(cmdfd, &rfd)) {
1492                         ttyread();
1493                         draw(SCREEN_UPDATE); 
1494                 }
1495                 while(XPending(xw.dis)) {
1496                         XNextEvent(xw.dis, &ev);
1497                         if (XFilterEvent(&ev, xw.win))
1498                                 continue;
1499                         if(handler[ev.type])
1500                                 (handler[ev.type])(&ev);
1501                 }
1502         }
1503 }
1504
1505 int
1506 main(int argc, char *argv[]) {
1507         int i;
1508         
1509         for(i = 1; i < argc; i++) {
1510                 switch(argv[i][0] != '-' || argv[i][2] ? -1 : argv[i][1]) {
1511                 case 't':
1512                         if(++i < argc) opt_title = argv[i];
1513                         break;
1514                 case 'e':
1515                         if(++i < argc) opt_cmd = argv[i];
1516                         break;
1517                 case 'v':
1518                 default:
1519                         die(USAGE);
1520                 }
1521         }
1522         setlocale(LC_CTYPE, "");
1523         tnew(80, 24);
1524         ttynew();
1525         xinit();
1526         selinit();
1527         run();
1528         return 0;
1529 }