JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
Initial Xft support for st. More to follow.
[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 <stdbool.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <signal.h>
14 #include <sys/ioctl.h>
15 #include <sys/select.h>
16 #include <sys/stat.h>
17 #include <sys/time.h>
18 #include <sys/types.h>
19 #include <sys/wait.h>
20 #include <time.h>
21 #include <unistd.h>
22 #include <X11/Xatom.h>
23 #include <X11/Xlib.h>
24 #include <X11/Xutil.h>
25 #include <X11/cursorfont.h>
26 #include <X11/keysym.h>
27 #include <X11/extensions/Xdbe.h>
28 #include <X11/Xft/Xft.h>
29 #define Glyph Glyph_
30 #define Font Font_
31
32 #if   defined(__linux)
33  #include <pty.h>
34 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
35  #include <util.h>
36 #elif defined(__FreeBSD__) || defined(__DragonFly__)
37  #include <libutil.h>
38 #endif
39
40 #define USAGE \
41         "st " VERSION " (c) 2010-2012 st engineers\n" \
42         "usage: st [-t title] [-c class] [-g geometry]" \
43         " [-w windowid] [-v] [-f file] [-e command...]\n"
44
45 /* XEMBED messages */
46 #define XEMBED_FOCUS_IN  4
47 #define XEMBED_FOCUS_OUT 5
48
49 /* Arbitrary sizes */
50 #define ESC_BUF_SIZ   256
51 #define ESC_ARG_SIZ   16
52 #define STR_BUF_SIZ   256
53 #define STR_ARG_SIZ   16
54 #define DRAW_BUF_SIZ  20*1024
55 #define UTF_SIZ       4
56 #define XK_NO_MOD     UINT_MAX
57 #define XK_ANY_MOD    0
58
59 #define REDRAW_TIMEOUT (80*1000) /* 80 ms */
60
61 #define SERRNO strerror(errno)
62 #define MIN(a, b)  ((a) < (b) ? (a) : (b))
63 #define MAX(a, b)  ((a) < (b) ? (b) : (a))
64 #define LEN(a)     (sizeof(a) / sizeof(a[0]))
65 #define DEFAULT(a, b)     (a) = (a) ? (a) : (b)
66 #define BETWEEN(x, a, b)  ((a) <= (x) && (x) <= (b))
67 #define LIMIT(x, a, b)    (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
68 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
69 #define IS_SET(flag) (term.mode & (flag))
70 #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + (t1.tv_usec-t2.tv_usec)/1000)
71 #define X2COL(x) (((x) - BORDER)/xw.cw)
72 #define Y2ROW(y) (((y) - BORDER)/xw.ch)
73
74 enum glyph_attribute {
75         ATTR_NULL      = 0,
76         ATTR_REVERSE   = 1,
77         ATTR_UNDERLINE = 2,
78         ATTR_BOLD      = 4,
79         ATTR_GFX       = 8,
80         ATTR_ITALIC    = 16,
81         ATTR_BLINK     = 32,
82 };
83
84 enum cursor_movement {
85         CURSOR_UP,
86         CURSOR_DOWN,
87         CURSOR_LEFT,
88         CURSOR_RIGHT,
89         CURSOR_SAVE,
90         CURSOR_LOAD
91 };
92
93 enum cursor_state {
94         CURSOR_DEFAULT  = 0,
95         CURSOR_HIDE     = 1,
96         CURSOR_WRAPNEXT = 2
97 };
98
99 enum glyph_state {
100         GLYPH_SET   = 1,
101         GLYPH_DIRTY = 2
102 };
103
104 enum term_mode {
105         MODE_WRAP       = 1,
106         MODE_INSERT      = 2,
107         MODE_APPKEYPAD   = 4,
108         MODE_ALTSCREEN   = 8,
109         MODE_CRLF       = 16,
110         MODE_MOUSEBTN    = 32,
111         MODE_MOUSEMOTION = 64,
112         MODE_MOUSE       = 32|64,
113         MODE_REVERSE     = 128
114 };
115
116 enum escape_state {
117         ESC_START      = 1,
118         ESC_CSI = 2,
119         ESC_STR = 4, /* DSC, OSC, PM, APC */
120         ESC_ALTCHARSET = 8,
121         ESC_STR_END    = 16, /* a final string was encountered */
122 };
123
124 enum window_state {
125         WIN_VISIBLE = 1,
126         WIN_REDRAW  = 2,
127         WIN_FOCUSED = 4
128 };
129
130 /* bit macro */
131 #undef B0
132 enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 };
133
134 typedef unsigned char uchar;
135 typedef unsigned int uint;
136 typedef unsigned long ulong;
137 typedef unsigned short ushort;
138
139 typedef struct {
140         char c[UTF_SIZ];     /* character code */
141         uchar mode;  /* attribute flags */
142         ushort fg;   /* foreground  */
143         ushort bg;   /* background  */
144         uchar state; /* state flags    */
145 } Glyph;
146
147 typedef Glyph* Line;
148
149 typedef struct {
150         Glyph attr;      /* current char attributes */
151         int x;
152         int y;
153         char state;
154 } TCursor;
155
156 /* CSI Escape sequence structs */
157 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
158 typedef struct {
159         char buf[ESC_BUF_SIZ]; /* raw string */
160         int len;               /* raw string length */
161         char priv;
162         int arg[ESC_ARG_SIZ];
163         int narg;             /* nb of args */
164         char mode;
165 } CSIEscape;
166
167 /* STR Escape sequence structs */
168 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
169 typedef struct {
170         char type;           /* ESC type ... */
171         char buf[STR_BUF_SIZ]; /* raw string */
172         int len;               /* raw string length */
173         char *args[STR_ARG_SIZ];
174         int narg;             /* nb of args */
175 } STREscape;
176
177 /* Internal representation of the screen */
178 typedef struct {
179         int row;        /* nb row */
180         int col;        /* nb col */
181         Line* line;     /* screen */
182         Line* alt;      /* alternate screen */
183         bool* dirty;    /* dirtyness of lines */
184         TCursor c;      /* cursor */
185         int top;        /* top    scroll limit */
186         int bot;        /* bottom scroll limit */
187         int mode;       /* terminal mode flags */
188         int esc;        /* escape state flags */
189         bool *tabs;
190 } Term;
191
192 /* Purely graphic info */
193 typedef struct {
194         Display* dpy;
195         Colormap cmap;
196         Window win;
197         XdbeBackBuffer buf;
198         Atom xembed;
199         XIM xim;
200         XIC xic;
201         XftDraw *xft_draw;
202         Visual *vis;
203         int scr;
204         Bool isfixed; /* is fixed geometry? */
205         int fx, fy, fw, fh; /* fixed geometry */
206         int tw, th; /* tty width and height */
207         int w;  /* window width */
208         int h;  /* window height */
209         int ch; /* char height */
210         int cw; /* char width  */
211         char state; /* focus, redraw, visible */
212 } XWindow;
213
214 typedef struct {
215         KeySym k;
216         uint mask;
217         char s[ESC_BUF_SIZ];
218 } Key;
219
220 /* TODO: use better name for vars... */
221 typedef struct {
222         int mode;
223         int bx, by;
224         int ex, ey;
225         struct {int x, y;} b, e;
226         char *clip;
227         Atom xtarget;
228         bool alt;
229         struct timeval tclick1;
230         struct timeval tclick2;
231 } Selection;
232
233 #include "config.h"
234
235 /* Font structure */
236 typedef struct {
237         int ascent;
238         int descent;
239         short lbearing;
240         short rbearing;
241         XFontSet set;
242         XftFont* xft_set;
243 } Font;
244
245 /* Drawing Context */
246 typedef struct {
247         ulong col[LEN(colorname) < 256 ? 256 : LEN(colorname)];
248         XftColor xft_col[LEN(colorname) < 256 ? 256 : LEN(colorname)];
249         GC gc;
250         Font font, bfont, ifont, ibfont;
251 } DC;
252
253 static void die(const char*, ...);
254 static void draw(void);
255 static void redraw(void);
256 static void drawregion(int, int, int, int);
257 static void execsh(void);
258 static void sigchld(int);
259 static void run(void);
260
261 static void csidump(void);
262 static void csihandle(void);
263 static void csiparse(void);
264 static void csireset(void);
265 static void strdump(void);
266 static void strhandle(void);
267 static void strparse(void);
268 static void strreset(void);
269
270 static void tclearregion(int, int, int, int);
271 static void tcursor(int);
272 static void tdeletechar(int);
273 static void tdeleteline(int);
274 static void tinsertblank(int);
275 static void tinsertblankline(int);
276 static void tmoveto(int, int);
277 static void tnew(int, int);
278 static void tnewline(int);
279 static void tputtab(bool);
280 static void tputc(char*);
281 static void treset(void);
282 static int tresize(int, int);
283 static void tscrollup(int, int);
284 static void tscrolldown(int, int);
285 static void tsetattr(int*, int);
286 static void tsetchar(char*);
287 static void tsetscroll(int, int);
288 static void tswapscreen(void);
289 static void tsetdirt(int, int);
290 static void tsetmode(bool, bool, int *, int);
291 static void tfulldirt(void);
292
293 static void ttynew(void);
294 static void ttyread(void);
295 static void ttyresize(int, int);
296 static void ttywrite(const char *, size_t);
297
298 static void xdraws(char *, Glyph, int, int, int, int);
299 static void xhints(void);
300 static void xclear(int, int, int, int);
301 static void xdrawcursor(void);
302 static void xinit(void);
303 static void xloadcols(void);
304 static void xresettitle(void);
305 static void xseturgency(int);
306 static void xsetsel(char*);
307 static void xresize(int, int);
308
309 static void expose(XEvent *);
310 static void visibility(XEvent *);
311 static void unmap(XEvent *);
312 static char* kmap(KeySym, uint);
313 static void kpress(XEvent *);
314 static void cmessage(XEvent *);
315 static void resize(XEvent *);
316 static void focus(XEvent *);
317 static void brelease(XEvent *);
318 static void bpress(XEvent *);
319 static void bmotion(XEvent *);
320 static void selnotify(XEvent *);
321 static void selclear(XEvent *);
322 static void selrequest(XEvent *);
323
324 static void selinit(void);
325 static inline bool selected(int, int);
326 static void selcopy(void);
327 static void selpaste(void);
328 static void selscroll(int, int);
329
330 static int utf8decode(char *, long *);
331 static int utf8encode(long *, char *);
332 static int utf8size(char *);
333 static int isfullutf8(char *, int);
334
335 static void *xmalloc(size_t);
336 static void *xrealloc(void *, size_t);
337 static void *xcalloc(size_t nmemb, size_t size);
338
339 static void (*handler[LASTEvent])(XEvent *) = {
340         [KeyPress] = kpress,
341         [ClientMessage] = cmessage,
342         [ConfigureNotify] = resize,
343         [VisibilityNotify] = visibility,
344         [UnmapNotify] = unmap,
345         [Expose] = expose,
346         [FocusIn] = focus,
347         [FocusOut] = focus,
348         [MotionNotify] = bmotion,
349         [ButtonPress] = bpress,
350         [ButtonRelease] = brelease,
351         [SelectionClear] = selclear,
352         [SelectionNotify] = selnotify,
353         [SelectionRequest] = selrequest,
354 };
355
356 /* Globals */
357 static DC dc;
358 static XWindow xw;
359 static Term term;
360 static CSIEscape csiescseq;
361 static STREscape strescseq;
362 static int cmdfd;
363 static pid_t pid;
364 static Selection sel;
365 static int iofd = -1;
366 static char **opt_cmd  = NULL;
367 static char *opt_io    = NULL;
368 static char *opt_title = NULL;
369 static char *opt_embed = NULL;
370 static char *opt_class = NULL;
371
372 void *
373 xmalloc(size_t len) {
374         void *p = malloc(len);
375         if(!p)
376                 die("Out of memory\n");
377         return p;
378 }
379
380 void *
381 xrealloc(void *p, size_t len) {
382         if((p = realloc(p, len)) == NULL)
383                 die("Out of memory\n");
384         return p;
385 }
386
387 void *
388 xcalloc(size_t nmemb, size_t size) {
389         void *p = calloc(nmemb, size);
390         if(!p)
391                 die("Out of memory\n");
392         return p;
393 }
394
395 int
396 utf8decode(char *s, long *u) {
397         uchar c;
398         int i, n, rtn;
399
400         rtn = 1;
401         c = *s;
402         if(~c & B7) { /* 0xxxxxxx */
403                 *u = c;
404                 return rtn;
405         } else if((c & (B7|B6|B5)) == (B7|B6)) { /* 110xxxxx */
406                 *u = c&(B4|B3|B2|B1|B0);
407                 n = 1;
408         } else if((c & (B7|B6|B5|B4)) == (B7|B6|B5)) { /* 1110xxxx */
409                 *u = c&(B3|B2|B1|B0);
410                 n = 2;
411         } else if((c & (B7|B6|B5|B4|B3)) == (B7|B6|B5|B4)) { /* 11110xxx */
412                 *u = c & (B2|B1|B0);
413                 n = 3;
414         } else
415                 goto invalid;
416         for(i = n, ++s; i > 0; --i, ++rtn, ++s) {
417                 c = *s;
418                 if((c & (B7|B6)) != B7) /* 10xxxxxx */
419                         goto invalid;
420                 *u <<= 6;
421                 *u |= c & (B5|B4|B3|B2|B1|B0);
422         }
423         if((n == 1 && *u < 0x80) ||
424            (n == 2 && *u < 0x800) ||
425            (n == 3 && *u < 0x10000) ||
426            (*u >= 0xD800 && *u <= 0xDFFF))
427                 goto invalid;
428         return rtn;
429 invalid:
430         *u = 0xFFFD;
431         return rtn;
432 }
433
434 int
435 utf8encode(long *u, char *s) {
436         uchar *sp;
437         ulong uc;
438         int i, n;
439
440         sp = (uchar*) s;
441         uc = *u;
442         if(uc < 0x80) {
443                 *sp = uc; /* 0xxxxxxx */
444                 return 1;
445         } else if(*u < 0x800) {
446                 *sp = (uc >> 6) | (B7|B6); /* 110xxxxx */
447                 n = 1;
448         } else if(uc < 0x10000) {
449                 *sp = (uc >> 12) | (B7|B6|B5); /* 1110xxxx */
450                 n = 2;
451         } else if(uc <= 0x10FFFF) {
452                 *sp = (uc >> 18) | (B7|B6|B5|B4); /* 11110xxx */
453                 n = 3;
454         } else {
455                 goto invalid;
456         }
457         for(i=n,++sp; i>0; --i,++sp)
458                 *sp = ((uc >> 6*(i-1)) & (B5|B4|B3|B2|B1|B0)) | B7; /* 10xxxxxx */
459         return n+1;
460 invalid:
461         /* U+FFFD */
462         *s++ = '\xEF';
463         *s++ = '\xBF';
464         *s = '\xBD';
465         return 3;
466 }
467
468 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
469    UTF-8 otherwise return 0 */
470 int
471 isfullutf8(char *s, int b) {
472         uchar *c1, *c2, *c3;
473
474         c1 = (uchar *) s;
475         c2 = (uchar *) ++s;
476         c3 = (uchar *) ++s;
477         if(b < 1)
478                 return 0;
479         else if((*c1&(B7|B6|B5)) == (B7|B6) && b == 1)
480                 return 0;
481         else if((*c1&(B7|B6|B5|B4)) == (B7|B6|B5) &&
482             ((b == 1) ||
483             ((b == 2) && (*c2&(B7|B6)) == B7)))
484                 return 0;
485         else if((*c1&(B7|B6|B5|B4|B3)) == (B7|B6|B5|B4) &&
486             ((b == 1) ||
487             ((b == 2) && (*c2&(B7|B6)) == B7) ||
488             ((b == 3) && (*c2&(B7|B6)) == B7 && (*c3&(B7|B6)) == B7)))
489                 return 0;
490         else
491                 return 1;
492 }
493
494 int
495 utf8size(char *s) {
496         uchar c = *s;
497
498         if(~c&B7)
499                 return 1;
500         else if((c&(B7|B6|B5)) == (B7|B6))
501                 return 2;
502         else if((c&(B7|B6|B5|B4)) == (B7|B6|B5))
503                 return 3;
504         else
505                 return 4;
506 }
507
508 void
509 selinit(void) {
510         memset(&sel.tclick1, 0, sizeof(sel.tclick1));
511         memset(&sel.tclick2, 0, sizeof(sel.tclick2));
512         sel.mode = 0;
513         sel.bx = -1;
514         sel.clip = NULL;
515         sel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
516         if(sel.xtarget == None)
517                 sel.xtarget = XA_STRING;
518 }
519
520 static inline bool
521 selected(int x, int y) {
522         if(sel.ey == y && sel.by == y) {
523                 int bx = MIN(sel.bx, sel.ex);
524                 int ex = MAX(sel.bx, sel.ex);
525                 return BETWEEN(x, bx, ex);
526         }
527         return ((sel.b.y < y&&y < sel.e.y) || (y==sel.e.y && x<=sel.e.x))
528                 || (y==sel.b.y && x>=sel.b.x && (x<=sel.e.x || sel.b.y!=sel.e.y));
529 }
530
531 void
532 getbuttoninfo(XEvent *e, int *b, int *x, int *y) {
533         if(b)
534                 *b = e->xbutton.button;
535
536         *x = X2COL(e->xbutton.x);
537         *y = Y2ROW(e->xbutton.y);
538         sel.b.x = sel.by < sel.ey ? sel.bx : sel.ex;
539         sel.b.y = MIN(sel.by, sel.ey);
540         sel.e.x = sel.by < sel.ey ? sel.ex : sel.bx;
541         sel.e.y = MAX(sel.by, sel.ey);
542 }
543
544 void
545 mousereport(XEvent *e) {
546         int x = X2COL(e->xbutton.x);
547         int y = Y2ROW(e->xbutton.y);
548         int button = e->xbutton.button;
549         int state = e->xbutton.state;
550         char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
551         static int ob, ox, oy;
552
553         /* from urxvt */
554         if(e->xbutton.type == MotionNotify) {
555                 if(!IS_SET(MODE_MOUSEMOTION) || (x == ox && y == oy))
556                         return;
557                 button = ob + 32;
558                 ox = x, oy = y;
559         } else if(e->xbutton.type == ButtonRelease || button == AnyButton) {
560                 button = 3;
561         } else {
562                 button -= Button1;
563                 if(button >= 3)
564                         button += 64 - 3;
565                 if(e->xbutton.type == ButtonPress) {
566                         ob = button;
567                         ox = x, oy = y;
568                 }
569         }
570
571         buf[3] = 32 + button + (state & ShiftMask ? 4 : 0)
572                 + (state & Mod4Mask    ? 8  : 0)
573                 + (state & ControlMask ? 16 : 0);
574
575         ttywrite(buf, sizeof(buf));
576 }
577
578 void
579 bpress(XEvent *e) {
580         if(IS_SET(MODE_MOUSE))
581                 mousereport(e);
582         else if(e->xbutton.button == Button1) {
583                 if(sel.bx != -1) {
584                         sel.bx = -1;
585                         tsetdirt(sel.b.y, sel.e.y);
586                         draw();
587                 }
588                 sel.mode = 1;
589                 sel.ex = sel.bx = X2COL(e->xbutton.x);
590                 sel.ey = sel.by = Y2ROW(e->xbutton.y);
591         }
592 }
593
594 void
595 selcopy(void) {
596         char *str, *ptr;
597         int x, y, bufsize, is_selected = 0;
598
599         if(sel.bx == -1)
600                 str = NULL;
601
602         else {
603                 bufsize = (term.col+1) * (sel.e.y-sel.b.y+1) * UTF_SIZ;
604                 ptr = str = xmalloc(bufsize);
605
606                 /* append every set & selected glyph to the selection */
607                 for(y = 0; y < term.row; y++) {
608                         for(x = 0; x < term.col; x++) {
609                                 int size;
610                                 char *p;
611                                 Glyph *gp = &term.line[y][x];
612
613                                 if(!(is_selected = selected(x, y)))
614                                         continue;
615                                 p = (gp->state & GLYPH_SET) ? gp->c : " ";
616                                 size = utf8size(p);
617                                 memcpy(ptr, p, size);
618                                 ptr += size;
619                         }
620                         /* \n at the end of every selected line except for the last one */
621                         if(is_selected && y < sel.e.y)
622                                 *ptr++ = '\n';
623                 }
624                 *ptr = 0;
625         }
626         sel.alt = IS_SET(MODE_ALTSCREEN);
627         xsetsel(str);
628 }
629
630 void
631 selnotify(XEvent *e) {
632         ulong nitems, ofs, rem;
633         int format;
634         uchar *data;
635         Atom type;
636
637         ofs = 0;
638         do {
639                 if(XGetWindowProperty(xw.dpy, xw.win, XA_PRIMARY, ofs, BUFSIZ/4,
640                                         False, AnyPropertyType, &type, &format,
641                                         &nitems, &rem, &data)) {
642                         fprintf(stderr, "Clipboard allocation failed\n");
643                         return;
644                 }
645                 ttywrite((const char *) data, nitems * format / 8);
646                 XFree(data);
647                 /* number of 32-bit chunks returned */
648                 ofs += nitems * format / 32;
649         } while(rem > 0);
650 }
651
652 void
653 selpaste() {
654         XConvertSelection(xw.dpy, XA_PRIMARY, sel.xtarget, XA_PRIMARY, xw.win, CurrentTime);
655 }
656
657 void selclear(XEvent *e) {
658         if(sel.bx == -1)
659                 return;
660         sel.bx = -1;
661         tsetdirt(sel.b.y, sel.e.y);
662 }
663
664 void
665 selrequest(XEvent *e) {
666         XSelectionRequestEvent *xsre;
667         XSelectionEvent xev;
668         Atom xa_targets;
669
670         xsre = (XSelectionRequestEvent *) e;
671         xev.type = SelectionNotify;
672         xev.requestor = xsre->requestor;
673         xev.selection = xsre->selection;
674         xev.target = xsre->target;
675         xev.time = xsre->time;
676         /* reject */
677         xev.property = None;
678
679         xa_targets = XInternAtom(xw.dpy, "TARGETS", 0);
680         if(xsre->target == xa_targets) {
681                 /* respond with the supported type */
682                 Atom string = sel.xtarget;
683                 XChangeProperty(xsre->display, xsre->requestor, xsre->property,
684                                 XA_ATOM, 32, PropModeReplace,
685                                 (uchar *) &string, 1);
686                 xev.property = xsre->property;
687         } else if(xsre->target == sel.xtarget && sel.clip != NULL) {
688                 XChangeProperty(xsre->display, xsre->requestor, xsre->property,
689                                 xsre->target, 8, PropModeReplace,
690                                 (uchar *) sel.clip, strlen(sel.clip));
691                 xev.property = xsre->property;
692         }
693
694         /* all done, send a notification to the listener */
695         if(!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *) &xev))
696                 fprintf(stderr, "Error sending SelectionNotify event\n");
697 }
698
699 void
700 xsetsel(char *str) {
701         /* register the selection for both the clipboard and the primary */
702         Atom clipboard;
703
704         free(sel.clip);
705         sel.clip = str;
706
707         XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
708
709         clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
710         XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);
711 }
712
713 void
714 brelease(XEvent *e) {
715         if(IS_SET(MODE_MOUSE)) {
716                 mousereport(e);
717                 return;
718         }
719         if(e->xbutton.button == Button2)
720                 selpaste();
721         else if(e->xbutton.button == Button1) {
722                 sel.mode = 0;
723                 getbuttoninfo(e, NULL, &sel.ex, &sel.ey);
724                 term.dirty[sel.ey] = 1;
725                 if(sel.bx == sel.ex && sel.by == sel.ey) {
726                         struct timeval now;
727                         sel.bx = -1;
728                         gettimeofday(&now, NULL);
729
730                         if(TIMEDIFF(now, sel.tclick2) <= TRIPLECLICK_TIMEOUT) {
731                                 /* triple click on the line */
732                                 sel.b.x = sel.bx = 0;
733                                 sel.e.x = sel.ex = term.col;
734                                 sel.b.y = sel.e.y = sel.ey;
735                                 selcopy();
736                         } else if(TIMEDIFF(now, sel.tclick1) <= DOUBLECLICK_TIMEOUT) {
737                                 /* double click to select word */
738                                 sel.bx = sel.ex;
739                                 while(sel.bx > 0 && term.line[sel.ey][sel.bx-1].state & GLYPH_SET &&
740                                           term.line[sel.ey][sel.bx-1].c[0] != ' ') sel.bx--;
741                                 sel.b.x = sel.bx;
742                                 while(sel.ex < term.col-1 && term.line[sel.ey][sel.ex+1].state & GLYPH_SET &&
743                                           term.line[sel.ey][sel.ex+1].c[0] != ' ') sel.ex++;
744                                 sel.e.x = sel.ex;
745                                 sel.b.y = sel.e.y = sel.ey;
746                                 selcopy();
747                         }
748                 } else
749                         selcopy();
750         }
751         memcpy(&sel.tclick2, &sel.tclick1, sizeof(struct timeval));
752         gettimeofday(&sel.tclick1, NULL);
753 }
754
755 void
756 bmotion(XEvent *e) {
757         if(IS_SET(MODE_MOUSE)) {
758                 mousereport(e);
759                 return;
760         }
761         if(sel.mode) {
762                 int oldey = sel.ey, oldex = sel.ex;
763                 getbuttoninfo(e, NULL, &sel.ex, &sel.ey);
764
765                 if(oldey != sel.ey || oldex != sel.ex) {
766                         int starty = MIN(oldey, sel.ey);
767                         int endy = MAX(oldey, sel.ey);
768                         tsetdirt(starty, endy);
769                 }
770         }
771 }
772
773 void
774 die(const char *errstr, ...) {
775         va_list ap;
776
777         va_start(ap, errstr);
778         vfprintf(stderr, errstr, ap);
779         va_end(ap);
780         exit(EXIT_FAILURE);
781 }
782
783 void
784 execsh(void) {
785         char **args;
786         char *envshell = getenv("SHELL");
787
788         unsetenv("COLUMNS");
789         unsetenv("LINES");
790         unsetenv("TERMCAP");
791
792         signal(SIGCHLD, SIG_DFL);
793         signal(SIGHUP, SIG_DFL);
794         signal(SIGINT, SIG_DFL);
795         signal(SIGQUIT, SIG_DFL);
796         signal(SIGTERM, SIG_DFL);
797         signal(SIGALRM, SIG_DFL);
798
799         DEFAULT(envshell, SHELL);
800         putenv("TERM="TNAME);
801         args = opt_cmd ? opt_cmd : (char*[]){envshell, "-i", NULL};
802         execvp(args[0], args);
803         exit(EXIT_FAILURE);
804 }
805
806 void
807 sigchld(int a) {
808         int stat = 0;
809         if(waitpid(pid, &stat, 0) < 0)
810                 die("Waiting for pid %hd failed: %s\n", pid, SERRNO);
811         if(WIFEXITED(stat))
812                 exit(WEXITSTATUS(stat));
813         else
814                 exit(EXIT_FAILURE);
815 }
816
817 void
818 ttynew(void) {
819         int m, s;
820
821         /* seems to work fine on linux, openbsd and freebsd */
822         struct winsize w = {term.row, term.col, 0, 0};
823         if(openpty(&m, &s, NULL, NULL, &w) < 0)
824                 die("openpty failed: %s\n", SERRNO);
825
826         switch(pid = fork()) {
827         case -1:
828                 die("fork failed\n");
829                 break;
830         case 0:
831                 setsid(); /* create a new process group */
832                 dup2(s, STDIN_FILENO);
833                 dup2(s, STDOUT_FILENO);
834                 dup2(s, STDERR_FILENO);
835                 if(ioctl(s, TIOCSCTTY, NULL) < 0)
836                         die("ioctl TIOCSCTTY failed: %s\n", SERRNO);
837                 close(s);
838                 close(m);
839                 execsh();
840                 break;
841         default:
842                 close(s);
843                 cmdfd = m;
844                 signal(SIGCHLD, sigchld);
845                 if(opt_io) {
846                         if(!strcmp(opt_io, "-")) {
847                                 iofd = STDOUT_FILENO;
848                         } else {
849                                 if((iofd = open(opt_io, O_WRONLY | O_CREAT, 0666)) < 0) {
850                                         fprintf(stderr, "Error opening %s:%s\n",
851                                                 opt_io, strerror(errno));
852                                 }
853                         }
854                 }
855         }
856 }
857
858 void
859 dump(char c) {
860         static int col;
861         fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
862         if(++col % 10 == 0)
863                 fprintf(stderr, "\n");
864 }
865
866 void
867 ttyread(void) {
868         static char buf[BUFSIZ];
869         static int buflen = 0;
870         char *ptr;
871         char s[UTF_SIZ];
872         int charsize; /* size of utf8 char in bytes */
873         long utf8c;
874         int ret;
875
876         /* append read bytes to unprocessed bytes */
877         if((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
878                 die("Couldn't read from shell: %s\n", SERRNO);
879
880         /* process every complete utf8 char */
881         buflen += ret;
882         ptr = buf;
883         while(buflen >= UTF_SIZ || isfullutf8(ptr,buflen)) {
884                 charsize = utf8decode(ptr, &utf8c);
885                 utf8encode(&utf8c, s);
886                 tputc(s);
887                 ptr    += charsize;
888                 buflen -= charsize;
889         }
890
891         /* keep any uncomplete utf8 char for the next call */
892         memmove(buf, ptr, buflen);
893 }
894
895 void
896 ttywrite(const char *s, size_t n) {
897         if(write(cmdfd, s, n) == -1)
898                 die("write error on tty: %s\n", SERRNO);
899 }
900
901 void
902 ttyresize(int x, int y) {
903         struct winsize w;
904
905         w.ws_row = term.row;
906         w.ws_col = term.col;
907         w.ws_xpixel = xw.tw;
908         w.ws_ypixel = xw.th;
909         if(ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
910                 fprintf(stderr, "Couldn't set window size: %s\n", SERRNO);
911 }
912
913 void
914 tsetdirt(int top, int bot)
915 {
916         int i;
917
918         LIMIT(top, 0, term.row-1);
919         LIMIT(bot, 0, term.row-1);
920
921         for(i = top; i <= bot; i++)
922                 term.dirty[i] = 1;
923 }
924
925 void
926 tfulldirt(void)
927 {
928         tsetdirt(0, term.row-1);
929 }
930
931 void
932 tcursor(int mode) {
933         static TCursor c;
934
935         if(mode == CURSOR_SAVE)
936                 c = term.c;
937         else if(mode == CURSOR_LOAD)
938                 term.c = c, tmoveto(c.x, c.y);
939 }
940
941 void
942 treset(void) {
943         unsigned i;
944         term.c = (TCursor){{
945                 .mode = ATTR_NULL,
946                 .fg = DefaultFG,
947                 .bg = DefaultBG
948         }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
949
950         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
951         for(i = TAB; i < term.col; i += TAB)
952                 term.tabs[i] = 1;
953         term.top = 0, term.bot = term.row - 1;
954         term.mode = MODE_WRAP;
955         tclearregion(0, 0, term.col-1, term.row-1);
956 }
957
958 void
959 tnew(int col, int row) {
960         /* set screen size */
961         term.row = row, term.col = col;
962         term.line = xmalloc(term.row * sizeof(Line));
963         term.alt  = xmalloc(term.row * sizeof(Line));
964         term.dirty = xmalloc(term.row * sizeof(*term.dirty));
965         term.tabs = xmalloc(term.col * sizeof(*term.tabs));
966
967         for(row = 0; row < term.row; row++) {
968                 term.line[row] = xmalloc(term.col * sizeof(Glyph));
969                 term.alt [row] = xmalloc(term.col * sizeof(Glyph));
970                 term.dirty[row] = 0;
971         }
972         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
973         /* setup screen */
974         treset();
975 }
976
977 void
978 tswapscreen(void) {
979         Line* tmp = term.line;
980         term.line = term.alt;
981         term.alt = tmp;
982         term.mode ^= MODE_ALTSCREEN;
983         tfulldirt();
984 }
985
986 void
987 tscrolldown(int orig, int n) {
988         int i;
989         Line temp;
990
991         LIMIT(n, 0, term.bot-orig+1);
992
993         tclearregion(0, term.bot-n+1, term.col-1, term.bot);
994
995         for(i = term.bot; i >= orig+n; i--) {
996                 temp = term.line[i];
997                 term.line[i] = term.line[i-n];
998                 term.line[i-n] = temp;
999
1000                 term.dirty[i] = 1;
1001                 term.dirty[i-n] = 1;
1002         }
1003
1004         selscroll(orig, n);
1005 }
1006
1007 void
1008 tscrollup(int orig, int n) {
1009         int i;
1010         Line temp;
1011         LIMIT(n, 0, term.bot-orig+1);
1012
1013         tclearregion(0, orig, term.col-1, orig+n-1);
1014
1015         for(i = orig; i <= term.bot-n; i++) {
1016                  temp = term.line[i];
1017                  term.line[i] = term.line[i+n];
1018                  term.line[i+n] = temp;
1019
1020                  term.dirty[i] = 1;
1021                  term.dirty[i+n] = 1;
1022         }
1023
1024         selscroll(orig, -n);
1025 }
1026
1027 void
1028 selscroll(int orig, int n) {
1029         if(sel.bx == -1)
1030                 return;
1031
1032         if(BETWEEN(sel.by, orig, term.bot) || BETWEEN(sel.ey, orig, term.bot)) {
1033                 if((sel.by += n) > term.bot || (sel.ey += n) < term.top) {
1034                         sel.bx = -1;
1035                         return;
1036                 }
1037                 if(sel.by < term.top) {
1038                         sel.by = term.top;
1039                         sel.bx = 0;
1040                 }
1041                 if(sel.ey > term.bot) {
1042                         sel.ey = term.bot;
1043                         sel.ex = term.col;
1044                 }
1045                 sel.b.y = sel.by, sel.b.x = sel.bx;
1046                 sel.e.y = sel.ey, sel.e.x = sel.ex;
1047         }
1048 }
1049
1050 void
1051 tnewline(int first_col) {
1052         int y = term.c.y;
1053         if(y == term.bot)
1054                 tscrollup(term.top, 1);
1055         else
1056                 y++;
1057         tmoveto(first_col ? 0 : term.c.x, y);
1058 }
1059
1060 void
1061 csiparse(void) {
1062         /* int noarg = 1; */
1063         char *p = csiescseq.buf;
1064
1065         csiescseq.narg = 0;
1066         if(*p == '?')
1067                 csiescseq.priv = 1, p++;
1068
1069         while(p < csiescseq.buf+csiescseq.len) {
1070                 while(isdigit(*p)) {
1071                         csiescseq.arg[csiescseq.narg] *= 10;
1072                         csiescseq.arg[csiescseq.narg] += *p++ - '0'/*, noarg = 0 */;
1073                 }
1074                 if(*p == ';' && csiescseq.narg+1 < ESC_ARG_SIZ)
1075                         csiescseq.narg++, p++;
1076                 else {
1077                         csiescseq.mode = *p;
1078                         csiescseq.narg++;
1079                         return;
1080                 }
1081         }
1082 }
1083
1084 void
1085 tmoveto(int x, int y) {
1086         LIMIT(x, 0, term.col-1);
1087         LIMIT(y, 0, term.row-1);
1088         term.c.state &= ~CURSOR_WRAPNEXT;
1089         term.c.x = x;
1090         term.c.y = y;
1091 }
1092
1093 void
1094 tsetchar(char *c) {
1095         term.dirty[term.c.y] = 1;
1096         term.line[term.c.y][term.c.x] = term.c.attr;
1097         memcpy(term.line[term.c.y][term.c.x].c, c, UTF_SIZ);
1098         term.line[term.c.y][term.c.x].state |= GLYPH_SET;
1099 }
1100
1101 void
1102 tclearregion(int x1, int y1, int x2, int y2) {
1103         int x, y, temp;
1104
1105         if(x1 > x2)
1106                 temp = x1, x1 = x2, x2 = temp;
1107         if(y1 > y2)
1108                 temp = y1, y1 = y2, y2 = temp;
1109
1110         LIMIT(x1, 0, term.col-1);
1111         LIMIT(x2, 0, term.col-1);
1112         LIMIT(y1, 0, term.row-1);
1113         LIMIT(y2, 0, term.row-1);
1114
1115         for(y = y1; y <= y2; y++) {
1116                 term.dirty[y] = 1;
1117                 for(x = x1; x <= x2; x++)
1118                         term.line[y][x].state = 0;
1119         }
1120 }
1121
1122 void
1123 tdeletechar(int n) {
1124         int src = term.c.x + n;
1125         int dst = term.c.x;
1126         int size = term.col - src;
1127
1128         term.dirty[term.c.y] = 1;
1129
1130         if(src >= term.col) {
1131                 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1132                 return;
1133         }
1134         memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph));
1135         tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1136 }
1137
1138 void
1139 tinsertblank(int n) {
1140         int src = term.c.x;
1141         int dst = src + n;
1142         int size = term.col - dst;
1143
1144         term.dirty[term.c.y] = 1;
1145
1146         if(dst >= term.col) {
1147                 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1148                 return;
1149         }
1150         memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph));
1151         tclearregion(src, term.c.y, dst - 1, term.c.y);
1152 }
1153
1154 void
1155 tinsertblankline(int n) {
1156         if(term.c.y < term.top || term.c.y > term.bot)
1157                 return;
1158
1159         tscrolldown(term.c.y, n);
1160 }
1161
1162 void
1163 tdeleteline(int n) {
1164         if(term.c.y < term.top || term.c.y > term.bot)
1165                 return;
1166
1167         tscrollup(term.c.y, n);
1168 }
1169
1170 void
1171 tsetattr(int *attr, int l) {
1172         int i;
1173
1174         for(i = 0; i < l; i++) {
1175                 switch(attr[i]) {
1176                 case 0:
1177                         term.c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD \
1178                                         | ATTR_ITALIC | ATTR_BLINK);
1179                         term.c.attr.fg = DefaultFG;
1180                         term.c.attr.bg = DefaultBG;
1181                         break;
1182                 case 1:
1183                         term.c.attr.mode |= ATTR_BOLD;
1184                         break;
1185                 case 3: /* enter standout (highlight) */
1186                         term.c.attr.mode |= ATTR_ITALIC;
1187                         break;
1188                 case 4:
1189                         term.c.attr.mode |= ATTR_UNDERLINE;
1190                         break;
1191                 case 5:
1192                         term.c.attr.mode |= ATTR_BLINK;
1193                         break;
1194                 case 7:
1195                         term.c.attr.mode |= ATTR_REVERSE;
1196                         break;
1197                 case 21:
1198                 case 22:
1199                         term.c.attr.mode &= ~ATTR_BOLD;
1200                         break;
1201                 case 23: /* leave standout (highlight) mode */
1202                         term.c.attr.mode &= ~ATTR_ITALIC;
1203                         break;
1204                 case 24:
1205                         term.c.attr.mode &= ~ATTR_UNDERLINE;
1206                         break;
1207                 case 25:
1208                         term.c.attr.mode &= ~ATTR_BLINK;
1209                         break;
1210                 case 27:
1211                         term.c.attr.mode &= ~ATTR_REVERSE;
1212                         break;
1213                 case 38:
1214                         if(i + 2 < l && attr[i + 1] == 5) {
1215                                 i += 2;
1216                                 if(BETWEEN(attr[i], 0, 255))
1217                                         term.c.attr.fg = attr[i];
1218                                 else
1219                                         fprintf(stderr, "erresc: bad fgcolor %d\n", attr[i]);
1220                         }
1221                         else
1222                                 fprintf(stderr, "erresc(38): gfx attr %d unknown\n", attr[i]);
1223                         break;
1224                 case 39:
1225                         term.c.attr.fg = DefaultFG;
1226                         break;
1227                 case 48:
1228                         if(i + 2 < l && attr[i + 1] == 5) {
1229                                 i += 2;
1230                                 if(BETWEEN(attr[i], 0, 255))
1231                                         term.c.attr.bg = attr[i];
1232                                 else
1233                                         fprintf(stderr, "erresc: bad bgcolor %d\n", attr[i]);
1234                         }
1235                         else
1236                                 fprintf(stderr, "erresc(48): gfx attr %d unknown\n", attr[i]);
1237                         break;
1238                 case 49:
1239                         term.c.attr.bg = DefaultBG;
1240                         break;
1241                 default:
1242                         if(BETWEEN(attr[i], 30, 37))
1243                                 term.c.attr.fg = attr[i] - 30;
1244                         else if(BETWEEN(attr[i], 40, 47))
1245                                 term.c.attr.bg = attr[i] - 40;
1246                         else if(BETWEEN(attr[i], 90, 97))
1247                                 term.c.attr.fg = attr[i] - 90 + 8;
1248                         else if(BETWEEN(attr[i], 100, 107))
1249                                 term.c.attr.bg = attr[i] - 100 + 8;
1250                         else
1251                                 fprintf(stderr, "erresc(default): gfx attr %d unknown\n", attr[i]), csidump();
1252                         break;
1253                 }
1254         }
1255 }
1256
1257 void
1258 tsetscroll(int t, int b) {
1259         int temp;
1260
1261         LIMIT(t, 0, term.row-1);
1262         LIMIT(b, 0, term.row-1);
1263         if(t > b) {
1264                 temp = t;
1265                 t = b;
1266                 b = temp;
1267         }
1268         term.top = t;
1269         term.bot = b;
1270 }
1271
1272 #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
1273
1274 void
1275 tsetmode(bool priv, bool set, int *args, int narg) {
1276         int *lim, mode;
1277
1278         for(lim = args + narg; args < lim; ++args) {
1279                 if(priv) {
1280                         switch(*args) {
1281                         case 1:
1282                                 MODBIT(term.mode, set, MODE_APPKEYPAD);
1283                                 break;
1284                         case 5: /* DECSCNM -- Reverve video */
1285                                 mode = term.mode;
1286                                 MODBIT(term.mode,set, MODE_REVERSE);
1287                                 if(mode != term.mode)
1288                                         redraw();
1289                                 break;
1290                         case 7:
1291                                 MODBIT(term.mode, set, MODE_WRAP);
1292                                 break;
1293                         case 20:
1294                                 MODBIT(term.mode, set, MODE_CRLF);
1295                                 break;
1296                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
1297                                 break;
1298                         case 25:
1299                                 MODBIT(term.c.state, !set, CURSOR_HIDE);
1300                                 break;
1301                         case 1000: /* 1000,1002: enable xterm mouse report */
1302                                 MODBIT(term.mode, set, MODE_MOUSEBTN);
1303                                 break;
1304                         case 1002:
1305                                 MODBIT(term.mode, set, MODE_MOUSEMOTION);
1306                                 break;
1307                         case 1049: /* = 1047 and 1048 */
1308                         case 47:
1309                         case 1047:
1310                                 if(IS_SET(MODE_ALTSCREEN))
1311                                         tclearregion(0, 0, term.col-1, term.row-1);
1312                                 if((set && !IS_SET(MODE_ALTSCREEN)) ||
1313                                     (!set && IS_SET(MODE_ALTSCREEN))) {
1314                                             tswapscreen();
1315                                 }
1316                                 if(*args != 1049)
1317                                         break;
1318                                 /* pass through */
1319                         case 1048:
1320                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1321                                 break;
1322                         default:
1323                                 fprintf(stderr,
1324                                         "erresc: unknown private set/reset mode %d\n",
1325                                         *args);
1326                                 break;
1327                         }
1328                 } else {
1329                         switch(*args) {
1330                         case 4:
1331                                 MODBIT(term.mode, set, MODE_INSERT);
1332                                 break;
1333                         default:
1334                                 fprintf(stderr,
1335                                         "erresc: unknown set/reset mode %d\n",
1336                                         *args);
1337                                 break;
1338                         }
1339                 }
1340         }
1341 }
1342 #undef MODBIT
1343
1344
1345 void
1346 csihandle(void) {
1347         switch(csiescseq.mode) {
1348         default:
1349         unknown:
1350                 fprintf(stderr, "erresc: unknown csi ");
1351                 csidump();
1352                 /* die(""); */
1353                 break;
1354         case '@': /* ICH -- Insert <n> blank char */
1355                 DEFAULT(csiescseq.arg[0], 1);
1356                 tinsertblank(csiescseq.arg[0]);
1357                 break;
1358         case 'A': /* CUU -- Cursor <n> Up */
1359         case 'e':
1360                 DEFAULT(csiescseq.arg[0], 1);
1361                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1362                 break;
1363         case 'B': /* CUD -- Cursor <n> Down */
1364                 DEFAULT(csiescseq.arg[0], 1);
1365                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1366                 break;
1367         case 'C': /* CUF -- Cursor <n> Forward */
1368         case 'a':
1369                 DEFAULT(csiescseq.arg[0], 1);
1370                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1371                 break;
1372         case 'D': /* CUB -- Cursor <n> Backward */
1373                 DEFAULT(csiescseq.arg[0], 1);
1374                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1375                 break;
1376         case 'E': /* CNL -- Cursor <n> Down and first col */
1377                 DEFAULT(csiescseq.arg[0], 1);
1378                 tmoveto(0, term.c.y+csiescseq.arg[0]);
1379                 break;
1380         case 'F': /* CPL -- Cursor <n> Up and first col */
1381                 DEFAULT(csiescseq.arg[0], 1);
1382                 tmoveto(0, term.c.y-csiescseq.arg[0]);
1383                 break;
1384         case 'g': /* TBC -- Tabulation clear */
1385                 switch (csiescseq.arg[0]) {
1386                 case 0: /* clear current tab stop */
1387                         term.tabs[term.c.x] = 0;
1388                         break;
1389                 case 3: /* clear all the tabs */
1390                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1391                         break;
1392                 default:
1393                         goto unknown;
1394                 }
1395                 break;
1396         case 'G': /* CHA -- Move to <col> */
1397         case '`': /* HPA */
1398                 DEFAULT(csiescseq.arg[0], 1);
1399                 tmoveto(csiescseq.arg[0]-1, term.c.y);
1400                 break;
1401         case 'H': /* CUP -- Move to <row> <col> */
1402         case 'f': /* HVP */
1403                 DEFAULT(csiescseq.arg[0], 1);
1404                 DEFAULT(csiescseq.arg[1], 1);
1405                 tmoveto(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1406                 break;
1407         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1408                 DEFAULT(csiescseq.arg[0], 1);
1409                 while(csiescseq.arg[0]--)
1410                         tputtab(1);
1411                 break;
1412         case 'J': /* ED -- Clear screen */
1413                 sel.bx = -1;
1414                 switch(csiescseq.arg[0]) {
1415                 case 0: /* below */
1416                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1417                         if(term.c.y < term.row-1)
1418                                 tclearregion(0, term.c.y+1, term.col-1, term.row-1);
1419                         break;
1420                 case 1: /* above */
1421                         if(term.c.y > 1)
1422                                 tclearregion(0, 0, term.col-1, term.c.y-1);
1423                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1424                         break;
1425                 case 2: /* all */
1426                         tclearregion(0, 0, term.col-1, term.row-1);
1427                         break;
1428                 default:
1429                         goto unknown;
1430                 }
1431                 break;
1432         case 'K': /* EL -- Clear line */
1433                 switch(csiescseq.arg[0]) {
1434                 case 0: /* right */
1435                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1436                         break;
1437                 case 1: /* left */
1438                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1439                         break;
1440                 case 2: /* all */
1441                         tclearregion(0, term.c.y, term.col-1, term.c.y);
1442                         break;
1443                 }
1444                 break;
1445         case 'S': /* SU -- Scroll <n> line up */
1446                 DEFAULT(csiescseq.arg[0], 1);
1447                 tscrollup(term.top, csiescseq.arg[0]);
1448                 break;
1449         case 'T': /* SD -- Scroll <n> line down */
1450                 DEFAULT(csiescseq.arg[0], 1);
1451                 tscrolldown(term.top, csiescseq.arg[0]);
1452                 break;
1453         case 'L': /* IL -- Insert <n> blank lines */
1454                 DEFAULT(csiescseq.arg[0], 1);
1455                 tinsertblankline(csiescseq.arg[0]);
1456                 break;
1457         case 'l': /* RM -- Reset Mode */
1458                 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1459                 break;
1460         case 'M': /* DL -- Delete <n> lines */
1461                 DEFAULT(csiescseq.arg[0], 1);
1462                 tdeleteline(csiescseq.arg[0]);
1463                 break;
1464         case 'X': /* ECH -- Erase <n> char */
1465                 DEFAULT(csiescseq.arg[0], 1);
1466                 tclearregion(term.c.x, term.c.y, term.c.x + csiescseq.arg[0], term.c.y);
1467                 break;
1468         case 'P': /* DCH -- Delete <n> char */
1469                 DEFAULT(csiescseq.arg[0], 1);
1470                 tdeletechar(csiescseq.arg[0]);
1471                 break;
1472         case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1473                 DEFAULT(csiescseq.arg[0], 1);
1474                 while(csiescseq.arg[0]--)
1475                         tputtab(0);
1476                 break;
1477         case 'd': /* VPA -- Move to <row> */
1478                 DEFAULT(csiescseq.arg[0], 1);
1479                 tmoveto(term.c.x, csiescseq.arg[0]-1);
1480                 break;
1481         case 'h': /* SM -- Set terminal mode */
1482                 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1483                 break;
1484         case 'm': /* SGR -- Terminal attribute (color) */
1485                 tsetattr(csiescseq.arg, csiescseq.narg);
1486                 break;
1487         case 'r': /* DECSTBM -- Set Scrolling Region */
1488                 if(csiescseq.priv)
1489                         goto unknown;
1490                 else {
1491                         DEFAULT(csiescseq.arg[0], 1);
1492                         DEFAULT(csiescseq.arg[1], term.row);
1493                         tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1494                         tmoveto(0, 0);
1495                 }
1496                 break;
1497         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1498                 tcursor(CURSOR_SAVE);
1499                 break;
1500         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1501                 tcursor(CURSOR_LOAD);
1502                 break;
1503         }
1504 }
1505
1506 void
1507 csidump(void) {
1508         int i;
1509         printf("ESC[");
1510         for(i = 0; i < csiescseq.len; i++) {
1511                 uint c = csiescseq.buf[i] & 0xff;
1512                 if(isprint(c)) putchar(c);
1513                 else if(c == '\n') printf("(\\n)");
1514                 else if(c == '\r') printf("(\\r)");
1515                 else if(c == 0x1b) printf("(\\e)");
1516                 else printf("(%02x)", c);
1517         }
1518         putchar('\n');
1519 }
1520
1521 void
1522 csireset(void) {
1523         memset(&csiescseq, 0, sizeof(csiescseq));
1524 }
1525
1526 void
1527 strhandle(void) {
1528         char *p;
1529
1530         /*
1531          * TODO: make this being useful in case of color palette change.
1532          */
1533         strparse();
1534
1535         p = strescseq.buf;
1536
1537         switch(strescseq.type) {
1538         case ']': /* OSC -- Operating System Command */
1539                 switch(p[0]) {
1540                 case '0':
1541                 case '1':
1542                 case '2':
1543                         /*
1544                          * TODO: Handle special chars in string, like umlauts.
1545                          */
1546                         if(p[1] == ';') {
1547                                 XStoreName(xw.dpy, xw.win, strescseq.buf+2);
1548                         }
1549                         break;
1550                 case ';':
1551                         XStoreName(xw.dpy, xw.win, strescseq.buf+1);
1552                         break;
1553                 case '4': /* TODO: Set color (arg0) to "rgb:%hexr/$hexg/$hexb" (arg1) */
1554                         break;
1555                 default:
1556                         fprintf(stderr, "erresc: unknown str ");
1557                         strdump();
1558                         break;
1559                 }
1560                 break;
1561         case 'k': /* old title set compatibility */
1562                 XStoreName(xw.dpy, xw.win, strescseq.buf);
1563                 break;
1564         case 'P': /* DSC -- Device Control String */
1565         case '_': /* APC -- Application Program Command */
1566         case '^': /* PM -- Privacy Message */
1567         default:
1568                 fprintf(stderr, "erresc: unknown str ");
1569                 strdump();
1570                 /* die(""); */
1571                 break;
1572         }
1573 }
1574
1575 void
1576 strparse(void) {
1577         /*
1578          * TODO: Implement parsing like for CSI when required.
1579          * Format: ESC type cmd ';' arg0 [';' argn] ESC \
1580          */
1581         return;
1582 }
1583
1584 void
1585 strdump(void) {
1586         int i;
1587         printf("ESC%c", strescseq.type);
1588         for(i = 0; i < strescseq.len; i++) {
1589                 uint c = strescseq.buf[i] & 0xff;
1590                 if(isprint(c)) putchar(c);
1591                 else if(c == '\n') printf("(\\n)");
1592                 else if(c == '\r') printf("(\\r)");
1593                 else if(c == 0x1b) printf("(\\e)");
1594                 else printf("(%02x)", c);
1595         }
1596         printf("ESC\\\n");
1597 }
1598
1599 void
1600 strreset(void) {
1601         memset(&strescseq, 0, sizeof(strescseq));
1602 }
1603
1604 void
1605 tputtab(bool forward) {
1606         unsigned x = term.c.x;
1607
1608         if(forward) {
1609                 if(x == term.col)
1610                         return;
1611                 for(++x; x < term.col && !term.tabs[x]; ++x)
1612                         /* nothing */ ;
1613         } else {
1614                 if(x == 0)
1615                         return;
1616                 for(--x; x > 0 && !term.tabs[x]; --x)
1617                         /* nothing */ ;
1618         }
1619         tmoveto(x, term.c.y);
1620 }
1621
1622 void
1623 tputc(char *c) {
1624         char ascii = *c;
1625
1626         if(iofd != -1)
1627                 write(iofd, c, 1);
1628
1629         if(term.esc & ESC_START) {
1630                 if(term.esc & ESC_CSI) {
1631                         csiescseq.buf[csiescseq.len++] = ascii;
1632                         if(BETWEEN(ascii, 0x40, 0x7E) || csiescseq.len >= ESC_BUF_SIZ) {
1633                                 term.esc = 0;
1634                                 csiparse(), csihandle();
1635                         }
1636                 } else if(term.esc & ESC_STR) {
1637                         switch(ascii) {
1638                         case '\033':
1639                                 term.esc = ESC_START | ESC_STR_END;
1640                                 break;
1641                         case '\a': /* backwards compatibility to xterm */
1642                                 term.esc = 0;
1643                                 strhandle();
1644                                 break;
1645                         default:
1646                                 strescseq.buf[strescseq.len++] = ascii;
1647                                 if(strescseq.len+1 >= STR_BUF_SIZ) {
1648                                         term.esc = 0;
1649                                         strhandle();
1650                                 }
1651                         }
1652                 } else if(term.esc & ESC_STR_END) {
1653                         term.esc = 0;
1654                         if(ascii == '\\')
1655                                 strhandle();
1656                 } else if(term.esc & ESC_ALTCHARSET) {
1657                         switch(ascii) {
1658                         case '0': /* Line drawing crap */
1659                                 term.c.attr.mode |= ATTR_GFX;
1660                                 break;
1661                         case 'B': /* Back to regular text */
1662                                 term.c.attr.mode &= ~ATTR_GFX;
1663                                 break;
1664                         default:
1665                                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
1666                         }
1667                         term.esc = 0;
1668                 } else {
1669                         switch(ascii) {
1670                         case '[':
1671                                 term.esc |= ESC_CSI;
1672                                 break;
1673                         case 'P': /* DCS -- Device Control String */
1674                         case '_': /* APC -- Application Program Command */
1675                         case '^': /* PM -- Privacy Message */
1676                         case ']': /* OSC -- Operating System Command */
1677                         case 'k': /* old title set compatibility */
1678                                 strreset();
1679                                 strescseq.type = ascii;
1680                                 term.esc |= ESC_STR;
1681                                 break;
1682                         case '(':
1683                                 term.esc |= ESC_ALTCHARSET;
1684                                 break;
1685                         case 'D': /* IND -- Linefeed */
1686                                 if(term.c.y == term.bot)
1687                                         tscrollup(term.top, 1);
1688                                 else
1689                                         tmoveto(term.c.x, term.c.y+1);
1690                                 term.esc = 0;
1691                                 break;
1692                         case 'E': /* NEL -- Next line */
1693                                 tnewline(1); /* always go to first col */
1694                                 term.esc = 0;
1695                                 break;
1696                         case 'H': /* HTS -- Horizontal tab stop */
1697                                 term.tabs[term.c.x] = 1;
1698                                 term.esc = 0;
1699                                 break;
1700                         case 'M': /* RI -- Reverse index */
1701                                 if(term.c.y == term.top)
1702                                         tscrolldown(term.top, 1);
1703                                 else
1704                                         tmoveto(term.c.x, term.c.y-1);
1705                                 term.esc = 0;
1706                                 break;
1707                         case 'c': /* RIS -- Reset to inital state */
1708                                 treset();
1709                                 term.esc = 0;
1710                                 xresettitle();
1711                                 break;
1712                         case '=': /* DECPAM -- Application keypad */
1713                                 term.mode |= MODE_APPKEYPAD;
1714                                 term.esc = 0;
1715                                 break;
1716                         case '>': /* DECPNM -- Normal keypad */
1717                                 term.mode &= ~MODE_APPKEYPAD;
1718                                 term.esc = 0;
1719                                 break;
1720                         case '7': /* DECSC -- Save Cursor */
1721                                 tcursor(CURSOR_SAVE);
1722                                 term.esc = 0;
1723                                 break;
1724                         case '8': /* DECRC -- Restore Cursor */
1725                                 tcursor(CURSOR_LOAD);
1726                                 term.esc = 0;
1727                                 break;
1728                         case '\\': /* ST -- Stop */
1729                                 term.esc = 0;
1730                                 break;
1731                         default:
1732                                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
1733                                     (uchar) ascii, isprint(ascii)?ascii:'.');
1734                                 term.esc = 0;
1735                         }
1736                 }
1737         } else {
1738                 if(sel.bx != -1 && BETWEEN(term.c.y, sel.by, sel.ey))
1739                         sel.bx = -1;
1740                 switch(ascii) {
1741                 case '\0': /* padding character, do nothing */
1742                         break;
1743                 case '\t':
1744                         tputtab(1);
1745                         break;
1746                 case '\b':
1747                         tmoveto(term.c.x-1, term.c.y);
1748                         break;
1749                 case '\r':
1750                         tmoveto(0, term.c.y);
1751                         break;
1752                 case '\f':
1753                 case '\v':
1754                 case '\n':
1755                         /* go to first col if the mode is set */
1756                         tnewline(IS_SET(MODE_CRLF));
1757                         break;
1758                 case '\a':
1759                         if(!(xw.state & WIN_FOCUSED))
1760                                 xseturgency(1);
1761                         break;
1762                 case '\033':
1763                         csireset();
1764                         term.esc = ESC_START;
1765                         break;
1766                 default:
1767                         if(IS_SET(MODE_WRAP) && term.c.state & CURSOR_WRAPNEXT)
1768                                 tnewline(1); /* always go to first col */
1769                         tsetchar(c);
1770                         if(term.c.x+1 < term.col)
1771                                 tmoveto(term.c.x+1, term.c.y);
1772                         else
1773                                 term.c.state |= CURSOR_WRAPNEXT;
1774                 }
1775         }
1776 }
1777
1778 int
1779 tresize(int col, int row) {
1780         int i, x;
1781         int minrow = MIN(row, term.row);
1782         int mincol = MIN(col, term.col);
1783         int slide = term.c.y - row + 1;
1784
1785         if(col < 1 || row < 1)
1786                 return 0;
1787
1788         /* free unneeded rows */
1789         i = 0;
1790         if(slide > 0) {
1791                 /* slide screen to keep cursor where we expect it -
1792                  * tscrollup would work here, but we can optimize to
1793                  * memmove because we're freeing the earlier lines */
1794                 for(/* i = 0 */; i < slide; i++) {
1795                         free(term.line[i]);
1796                         free(term.alt[i]);
1797                 }
1798                 memmove(term.line, term.line + slide, row * sizeof(Line));
1799                 memmove(term.alt, term.alt + slide, row * sizeof(Line));
1800         }
1801         for(i += row; i < term.row; i++) {
1802                 free(term.line[i]);
1803                 free(term.alt[i]);
1804         }
1805
1806         /* resize to new height */
1807         term.line = xrealloc(term.line, row * sizeof(Line));
1808         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
1809         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
1810         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
1811
1812         /* resize each row to new width, zero-pad if needed */
1813         for(i = 0; i < minrow; i++) {
1814                 term.dirty[i] = 1;
1815                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
1816                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
1817                 for(x = mincol; x < col; x++) {
1818                         term.line[i][x].state = 0;
1819                         term.alt[i][x].state = 0;
1820                 }
1821         }
1822
1823         /* allocate any new rows */
1824         for(/* i == minrow */; i < row; i++) {
1825                 term.dirty[i] = 1;
1826                 term.line[i] = xcalloc(col, sizeof(Glyph));
1827                 term.alt [i] = xcalloc(col, sizeof(Glyph));
1828         }
1829         if(col > term.col) {
1830                 bool *bp = term.tabs + term.col;
1831
1832                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
1833                 while(--bp > term.tabs && !*bp)
1834                         /* nothing */ ;
1835                 for(bp += TAB; bp < term.tabs + col; bp += TAB)
1836                         *bp = 1;
1837         }
1838         /* update terminal size */
1839         term.col = col, term.row = row;
1840         /* make use of the LIMIT in tmoveto */
1841         tmoveto(term.c.x, term.c.y);
1842         /* reset scrolling region */
1843         tsetscroll(0, row-1);
1844
1845         return (slide > 0);
1846 }
1847
1848 void
1849 xresize(int col, int row) {
1850         xw.tw = MAX(1, 2*BORDER + col * xw.cw);
1851         xw.th = MAX(1, 2*BORDER + row * xw.ch);
1852
1853         XftDrawChange(xw.xft_draw, xw.buf);
1854 }
1855
1856 void
1857 xloadcols(void) {
1858         int i, r, g, b;
1859         XRenderColor xft_color = { .alpha = 0 };
1860         ulong white = WhitePixel(xw.dpy, xw.scr);
1861
1862         /* load colors [0-15] colors and [256-LEN(colorname)[ (config.h) */
1863         for(i = 0; i < LEN(colorname); i++) {
1864                 if(!colorname[i])
1865                         continue;
1866                 if(!XftColorAllocName(xw.dpy, xw.vis, xw.cmap, colorname[i], &dc.xft_col[i])) {
1867                         dc.col[i] = white;
1868                         fprintf(stderr, "Could not allocate color '%s'\n", colorname[i]);
1869                 } else
1870                         dc.col[i] = dc.xft_col[i].pixel;
1871         }
1872
1873         /* load colors [16-255] ; same colors as xterm */
1874         for(i = 16, r = 0; r < 6; r++)
1875                 for(g = 0; g < 6; g++)
1876                         for(b = 0; b < 6; b++) {
1877                                 xft_color.red = r == 0 ? 0 : 0x3737 + 0x2828 * r;
1878                                 xft_color.green = g == 0 ? 0 : 0x3737 + 0x2828 * g;
1879                                 xft_color.blue = b == 0 ? 0 : 0x3737 + 0x2828 * b;
1880                                 if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &xft_color, &dc.xft_col[i])) {
1881                                         dc.col[i] = white;
1882                                         fprintf(stderr, "Could not allocate color %d\n", i);
1883                                 } else
1884                                         dc.col[i] = dc.xft_col[i].pixel;
1885                                 i++;
1886                         }
1887
1888         for(r = 0; r < 24; r++, i++) {
1889                 xft_color.red = xft_color.green = xft_color.blue = 0x0808 + 0x0a0a * r;
1890                 if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &xft_color, &dc.xft_col[i])) {
1891                         dc.col[i] = white;
1892                         fprintf(stderr, "Could not allocate color %d\n", i);
1893                 } else
1894                         dc.col[i] = dc.xft_col[i].pixel;
1895         }
1896 }
1897
1898 void
1899 xclear(int x1, int y1, int x2, int y2) {
1900         XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? DefaultFG : DefaultBG]);
1901         XFillRectangle(xw.dpy, xw.buf, dc.gc,
1902                        BORDER + x1 * xw.cw, BORDER + y1 * xw.ch,
1903                        (x2-x1+1) * xw.cw, (y2-y1+1) * xw.ch);
1904 }
1905
1906 void
1907 xhints(void) {
1908         XClassHint class = {opt_class ? opt_class : TNAME, TNAME};
1909         XWMHints wm = {.flags = InputHint, .input = 1};
1910         XSizeHints *sizeh = NULL;
1911
1912         sizeh = XAllocSizeHints();
1913         if(xw.isfixed == False) {
1914                 sizeh->flags = PSize | PResizeInc | PBaseSize;
1915                 sizeh->height = xw.h;
1916                 sizeh->width = xw.w;
1917                 sizeh->height_inc = xw.ch;
1918                 sizeh->width_inc = xw.cw;
1919                 sizeh->base_height = 2*BORDER;
1920                 sizeh->base_width = 2*BORDER;
1921         } else {
1922                 sizeh->flags = PMaxSize | PMinSize;
1923                 sizeh->min_width = sizeh->max_width = xw.fw;
1924                 sizeh->min_height = sizeh->max_height = xw.fh;
1925         }
1926
1927         XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, &class);
1928         XFree(sizeh);
1929 }
1930
1931 void
1932 xinitfont(Font *f, char *fontstr) {
1933         f->xft_set = XftFontOpenName(xw.dpy, xw.scr, fontstr);
1934
1935         if(!f->xft_set)
1936                 die("st: can't open font %s.\n", fontstr);
1937
1938         f->ascent = f->xft_set->ascent;
1939         f->descent = f->xft_set->descent;
1940         f->lbearing = 0;
1941         f->rbearing = f->xft_set->max_advance_width;
1942 }
1943
1944 void
1945 initfonts(char *fontstr, char *bfontstr, char *ifontstr, char *ibfontstr) {
1946         xinitfont(&dc.font, fontstr);
1947         xinitfont(&dc.bfont, bfontstr);
1948         xinitfont(&dc.ifont, ifontstr);
1949         xinitfont(&dc.ibfont, ibfontstr);
1950 }
1951
1952 void
1953 xinit(void) {
1954         XSetWindowAttributes attrs;
1955         Cursor cursor;
1956         Window parent;
1957         int sw, sh, major, minor;
1958
1959         if(!(xw.dpy = XOpenDisplay(NULL)))
1960                 die("Can't open display\n");
1961         xw.scr = XDefaultScreen(xw.dpy);
1962         xw.vis = XDefaultVisual(xw.dpy, xw.scr);
1963
1964         /* font */
1965         initfonts(FONT, BOLDFONT, ITALICFONT, ITALICBOLDFONT);
1966
1967         /* XXX: Assuming same size for bold font */
1968         xw.cw = dc.font.rbearing - dc.font.lbearing;
1969         xw.ch = dc.font.ascent + dc.font.descent;
1970
1971         /* colors */
1972         xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
1973         xloadcols();
1974
1975         /* adjust fixed window geometry */
1976         if(xw.isfixed) {
1977                 sw = DisplayWidth(xw.dpy, xw.scr);
1978                 sh = DisplayHeight(xw.dpy, xw.scr);
1979                 if(xw.fx < 0)
1980                         xw.fx = sw + xw.fx - xw.fw - 1;
1981                 if(xw.fy < 0)
1982                         xw.fy = sh + xw.fy - xw.fh - 1;
1983
1984                 xw.h = xw.fh;
1985                 xw.w = xw.fw;
1986         } else {
1987                 /* window - default size */
1988                 xw.h = 2*BORDER + term.row * xw.ch;
1989                 xw.w = 2*BORDER + term.col * xw.cw;
1990                 xw.fx = 0;
1991                 xw.fy = 0;
1992         }
1993
1994         attrs.background_pixel = dc.col[DefaultBG];
1995         attrs.border_pixel = dc.col[DefaultBG];
1996         attrs.bit_gravity = NorthWestGravity;
1997         attrs.event_mask = FocusChangeMask | KeyPressMask
1998                 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
1999                 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
2000         attrs.colormap = xw.cmap;
2001
2002         parent = opt_embed ? strtol(opt_embed, NULL, 0) : XRootWindow(xw.dpy, xw.scr);
2003         xw.win = XCreateWindow(xw.dpy, parent, xw.fx, xw.fy,
2004                         xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
2005                         xw.vis,
2006                         CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask
2007                         | CWColormap,
2008                         &attrs);
2009
2010         /* double buffering */
2011         if(!XdbeQueryExtension(xw.dpy, &major, &minor))
2012                 die("Xdbe extension is not present\n");
2013         xw.buf = XdbeAllocateBackBufferName(xw.dpy, xw.win, XdbeCopied);
2014
2015         /* Xft rendering context */
2016         xw.xft_draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
2017
2018         /* input methods */
2019         xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
2020         xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing
2021                                            | XIMStatusNothing, XNClientWindow, xw.win,
2022                                            XNFocusWindow, xw.win, NULL);
2023         /* gc */
2024         dc.gc = XCreateGC(xw.dpy, xw.win, 0, NULL);
2025
2026         /* white cursor, black outline */
2027         cursor = XCreateFontCursor(xw.dpy, XC_xterm);
2028         XDefineCursor(xw.dpy, xw.win, cursor);
2029         XRecolorCursor(xw.dpy, cursor,
2030                 &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff},
2031                 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
2032
2033         xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
2034
2035         xresettitle();
2036         XMapWindow(xw.dpy, xw.win);
2037         xhints();
2038         XSync(xw.dpy, 0);
2039 }
2040
2041 void
2042 xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
2043         int fg = base.fg, bg = base.bg, temp;
2044         int winx = BORDER+x*xw.cw, winy = BORDER+y*xw.ch + dc.font.ascent, width = charlen*xw.cw;
2045         Font *font = &dc.font;
2046         XGlyphInfo extents;
2047         int i;
2048
2049         /* only switch default fg/bg if term is in RV mode */
2050         if(IS_SET(MODE_REVERSE)) {
2051                 if(fg == DefaultFG)
2052                         fg = DefaultBG;
2053                 if(bg == DefaultBG)
2054                         bg = DefaultFG;
2055         }
2056
2057         if(base.mode & ATTR_REVERSE)
2058                 temp = fg, fg = bg, bg = temp;
2059
2060         if(base.mode & ATTR_BOLD) {
2061                 fg += 8;
2062                 font = &dc.bfont;
2063         }
2064
2065         if(base.mode & ATTR_ITALIC)
2066                 font = &dc.ifont;
2067         if(base.mode & (ATTR_ITALIC|ATTR_ITALIC))
2068                 font = &dc.ibfont;
2069
2070         XSetBackground(xw.dpy, dc.gc, dc.col[bg]);
2071         XSetForeground(xw.dpy, dc.gc, dc.col[fg]);
2072
2073         if(base.mode & ATTR_GFX) {
2074                 for(i = 0; i < bytelen; i++) {
2075                         char c = gfx[(uint)s[i] % 256];
2076                         if(c)
2077                                 s[i] = c;
2078                         else if(s[i] > 0x5f)
2079                                 s[i] -= 0x5f;
2080                 }
2081         }
2082
2083         XftTextExtentsUtf8(xw.dpy, font->xft_set, (FcChar8 *)s, bytelen, &extents);
2084         width = extents.xOff;
2085         XftDrawRect(xw.xft_draw, &dc.xft_col[bg], winx, winy - font->ascent, width, xw.ch);
2086         XftDrawStringUtf8(xw.xft_draw, &dc.xft_col[fg], font->xft_set, winx, winy, (FcChar8 *)s, bytelen);
2087
2088         if(base.mode & ATTR_UNDERLINE)
2089                 XDrawLine(xw.dpy, xw.buf, dc.gc, winx, winy+1, winx+width-1, winy+1);
2090 }
2091
2092 void
2093 xdrawcursor(void) {
2094         static int oldx = 0;
2095         static int oldy = 0;
2096         int sl;
2097         Glyph g = {{' '}, ATTR_NULL, DefaultBG, DefaultCS, 0};
2098
2099         LIMIT(oldx, 0, term.col-1);
2100         LIMIT(oldy, 0, term.row-1);
2101
2102         if(term.line[term.c.y][term.c.x].state & GLYPH_SET)
2103                 memcpy(g.c, term.line[term.c.y][term.c.x].c, UTF_SIZ);
2104
2105         /* remove the old cursor */
2106         if(term.line[oldy][oldx].state & GLYPH_SET) {
2107                 sl = utf8size(term.line[oldy][oldx].c);
2108                 xdraws(term.line[oldy][oldx].c, term.line[oldy][oldx], oldx, oldy, 1, sl);
2109         } else
2110                 xclear(oldx, oldy, oldx, oldy);
2111
2112         /* draw the new one */
2113         if(!(term.c.state & CURSOR_HIDE)) {
2114                 if(!(xw.state & WIN_FOCUSED))
2115                         g.bg = DefaultUCS;
2116
2117                 if(IS_SET(MODE_REVERSE))
2118                         g.mode |= ATTR_REVERSE, g.fg = DefaultCS, g.bg = DefaultFG;
2119
2120                 sl = utf8size(g.c);
2121                 xdraws(g.c, g, term.c.x, term.c.y, 1, sl);
2122                 oldx = term.c.x, oldy = term.c.y;
2123         }
2124 }
2125
2126 void
2127 xresettitle(void) {
2128         XStoreName(xw.dpy, xw.win, opt_title ? opt_title : "st");
2129 }
2130
2131 void
2132 redraw(void) {
2133         struct timespec tv = {0, REDRAW_TIMEOUT * 1000};
2134
2135         xclear(0, 0, xw.w, xw.h);
2136         tfulldirt();
2137         draw();
2138         XSync(xw.dpy, False); /* necessary for a good tput flash */
2139         nanosleep(&tv, NULL);
2140 }
2141
2142 void
2143 draw() {
2144         XdbeSwapInfo swpinfo[1] = {{xw.win, XdbeCopied}};
2145
2146         drawregion(0, 0, term.col, term.row);
2147         XdbeSwapBuffers(xw.dpy, swpinfo, 1);
2148 }
2149
2150 void
2151 drawregion(int x1, int y1, int x2, int y2) {
2152         int ic, ib, x, y, ox, sl;
2153         Glyph base, new;
2154         char buf[DRAW_BUF_SIZ];
2155         bool ena_sel = sel.bx != -1, alt = IS_SET(MODE_ALTSCREEN);
2156
2157         if((sel.alt && !alt) || (!sel.alt && alt))
2158                 ena_sel = 0;
2159         if(!(xw.state & WIN_VISIBLE))
2160                 return;
2161
2162         for(y = y1; y < y2; y++) {
2163                 if(!term.dirty[y])
2164                         continue;
2165                 xclear(0, y, term.col, y);
2166                 term.dirty[y] = 0;
2167                 base = term.line[y][0];
2168                 ic = ib = ox = 0;
2169                 for(x = x1; x < x2; x++) {
2170                         new = term.line[y][x];
2171                         if(ena_sel && *(new.c) && selected(x, y))
2172                                 new.mode ^= ATTR_REVERSE;
2173                         if(ib > 0 && (!(new.state & GLYPH_SET) || ATTRCMP(base, new) ||
2174                                                   ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
2175                                 xdraws(buf, base, ox, y, ic, ib);
2176                                 ic = ib = 0;
2177                         }
2178                         if(new.state & GLYPH_SET) {
2179                                 if(ib == 0) {
2180                                         ox = x;
2181                                         base = new;
2182                                 }
2183                                 sl = utf8size(new.c);
2184                                 memcpy(buf+ib, new.c, sl);
2185                                 ib += sl;
2186                                 ++ic;
2187                         }
2188                 }
2189                 if(ib > 0)
2190                         xdraws(buf, base, ox, y, ic, ib);
2191         }
2192         xdrawcursor();
2193 }
2194
2195 void
2196 expose(XEvent *ev) {
2197         XExposeEvent *e = &ev->xexpose;
2198         if(xw.state & WIN_REDRAW) {
2199                 if(!e->count)
2200                         xw.state &= ~WIN_REDRAW;
2201         }
2202 }
2203
2204 void
2205 visibility(XEvent *ev) {
2206         XVisibilityEvent *e = &ev->xvisibility;
2207         if(e->state == VisibilityFullyObscured)
2208                 xw.state &= ~WIN_VISIBLE;
2209         else if(!(xw.state & WIN_VISIBLE))
2210                 /* need a full redraw for next Expose, not just a buf copy */
2211                 xw.state |= WIN_VISIBLE | WIN_REDRAW;
2212 }
2213
2214 void
2215 unmap(XEvent *ev) {
2216         xw.state &= ~WIN_VISIBLE;
2217 }
2218
2219 void
2220 xseturgency(int add) {
2221         XWMHints *h = XGetWMHints(xw.dpy, xw.win);
2222         h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
2223         XSetWMHints(xw.dpy, xw.win, h);
2224         XFree(h);
2225 }
2226
2227 void
2228 focus(XEvent *ev) {
2229         if(ev->type == FocusIn) {
2230                 xw.state |= WIN_FOCUSED;
2231                 xseturgency(0);
2232         } else
2233                 xw.state &= ~WIN_FOCUSED;
2234 }
2235
2236 char*
2237 kmap(KeySym k, uint state) {
2238         int i;
2239         state &= ~Mod2Mask;
2240         for(i = 0; i < LEN(key); i++) {
2241                 uint mask = key[i].mask;
2242                 if(key[i].k == k && ((state & mask) == mask || (mask == XK_NO_MOD && !state)))
2243                         return (char*)key[i].s;
2244         }
2245         return NULL;
2246 }
2247
2248 void
2249 kpress(XEvent *ev) {
2250         XKeyEvent *e = &ev->xkey;
2251         KeySym ksym;
2252         char buf[32];
2253         char *customkey;
2254         int len;
2255         int meta;
2256         int shift;
2257         Status status;
2258
2259         meta = e->state & Mod1Mask;
2260         shift = e->state & ShiftMask;
2261         len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
2262
2263         /* 1. custom keys from config.h */
2264         if((customkey = kmap(ksym, e->state)))
2265                 ttywrite(customkey, strlen(customkey));
2266         /* 2. hardcoded (overrides X lookup) */
2267         else
2268                 switch(ksym) {
2269                 case XK_Up:
2270                 case XK_Down:
2271                 case XK_Left:
2272                 case XK_Right:
2273                         /* XXX: shift up/down doesn't work */
2274                         sprintf(buf, "\033%c%c", IS_SET(MODE_APPKEYPAD) ? 'O' : '[', (shift ? "dacb":"DACB")[ksym - XK_Left]);
2275                         ttywrite(buf, 3);
2276                         break;
2277                 case XK_Insert:
2278                         if(shift)
2279                                 selpaste();
2280                         break;
2281                 case XK_Return:
2282                         if(IS_SET(MODE_CRLF))
2283                                 ttywrite("\r\n", 2);
2284                         else
2285                                 ttywrite("\r", 1);
2286                         break;
2287                         /* 3. X lookup  */
2288                 default:
2289                         if(len > 0) {
2290                                 if(meta && len == 1)
2291                                         ttywrite("\033", 1);
2292                                 ttywrite(buf, len);
2293                         }
2294                         break;
2295                 }
2296 }
2297
2298 void
2299 cmessage(XEvent *e) {
2300         /* See xembed specs
2301            http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
2302         if(e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
2303                 if(e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
2304                         xw.state |= WIN_FOCUSED;
2305                         xseturgency(0);
2306                 } else if(e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
2307                         xw.state &= ~WIN_FOCUSED;
2308                 }
2309         }
2310 }
2311
2312 void
2313 resize(XEvent *e) {
2314         int col, row;
2315
2316         if(e->xconfigure.width == xw.w && e->xconfigure.height == xw.h)
2317                 return;
2318
2319         xw.w = e->xconfigure.width;
2320         xw.h = e->xconfigure.height;
2321         col = (xw.w - 2*BORDER) / xw.cw;
2322         row = (xw.h - 2*BORDER) / xw.ch;
2323         if(col == term.col && row == term.row)
2324                 return;
2325
2326         xclear(0, 0, xw.w, xw.h);
2327         tresize(col, row);
2328         xresize(col, row);
2329         ttyresize(col, row);
2330 }
2331
2332 void
2333 run(void) {
2334         XEvent ev;
2335         fd_set rfd;
2336         int xfd = XConnectionNumber(xw.dpy), i;
2337         struct timeval drawtimeout, *tv = NULL;
2338
2339         for(i = 0;; i++) {
2340                 FD_ZERO(&rfd);
2341                 FD_SET(cmdfd, &rfd);
2342                 FD_SET(xfd, &rfd);
2343                 if(select(MAX(xfd, cmdfd)+1, &rfd, NULL, NULL, tv) < 0) {
2344                         if(errno == EINTR)
2345                                 continue;
2346                         die("select failed: %s\n", SERRNO);
2347                 }
2348
2349                 /*
2350                  * Stop after a certain number of reads so the user does not
2351                  * feel like the system is stuttering.
2352                  */
2353                 if(i < 1000 && FD_ISSET(cmdfd, &rfd)) {
2354                         ttyread();
2355
2356                         /*
2357                          * Just wait a bit so it isn't disturbing the
2358                          * user and the system is able to write something.
2359                          */
2360                         drawtimeout.tv_sec = 0;
2361                         drawtimeout.tv_usec = 5;
2362                         tv = &drawtimeout;
2363                         continue;
2364                 }
2365                 i = 0;
2366                 tv = NULL;
2367
2368                 while(XPending(xw.dpy)) {
2369                         XNextEvent(xw.dpy, &ev);
2370                         if(XFilterEvent(&ev, xw.win))
2371                                 continue;
2372                         if(handler[ev.type])
2373                                 (handler[ev.type])(&ev);
2374                 }
2375
2376                 draw();
2377                 XFlush(xw.dpy);
2378         }
2379 }
2380
2381 int
2382 main(int argc, char *argv[]) {
2383         int i, bitm, xr, yr;
2384         unsigned int wr, hr;
2385
2386         xw.fw = xw.fh = xw.fx = xw.fy = 0;
2387         xw.isfixed = False;
2388
2389         for(i = 1; i < argc; i++) {
2390                 switch(argv[i][0] != '-' || argv[i][2] ? -1 : argv[i][1]) {
2391                 case 't':
2392                         if(++i < argc) opt_title = argv[i];
2393                         break;
2394                 case 'c':
2395                         if(++i < argc) opt_class = argv[i];
2396                         break;
2397                 case 'w':
2398                         if(++i < argc) opt_embed = argv[i];
2399                         break;
2400                 case 'f':
2401                         if(++i < argc) opt_io = argv[i];
2402                         break;
2403                 case 'e':
2404                         /* eat every remaining arguments */
2405                         if(++i < argc) opt_cmd = &argv[i];
2406                         goto run;
2407                 case 'g':
2408                         if(++i >= argc)
2409                                 break;
2410
2411                         bitm = XParseGeometry(argv[i], &xr, &yr, &wr, &hr);
2412                         if(bitm & XValue)
2413                                 xw.fx = xr;
2414                         if(bitm & YValue)
2415                                 xw.fy = yr;
2416                         if(bitm & WidthValue)
2417                                 xw.fw = (int)wr;
2418                         if(bitm & HeightValue)
2419                                 xw.fh = (int)hr;
2420                         if(bitm & XNegative && xw.fx == 0)
2421                                 xw.fx = -1;
2422                         if(bitm & XNegative && xw.fy == 0)
2423                                 xw.fy = -1;
2424
2425                         if(xw.fh != 0 && xw.fw != 0)
2426                                 xw.isfixed = True;
2427                         break;
2428                 case 'v':
2429                 default:
2430                         die(USAGE);
2431                 }
2432         }
2433
2434  run:
2435         setlocale(LC_CTYPE, "");
2436         tnew(80, 24);
2437         ttynew();
2438         xinit();
2439         selinit();
2440         run();
2441         return 0;
2442 }
2443