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