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