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