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