JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
Adding a way to ignore bits in the state.
[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 <pwd.h>
9 #include <stdarg.h>
10 #include <stdbool.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <signal.h>
15 #include <sys/ioctl.h>
16 #include <sys/select.h>
17 #include <sys/stat.h>
18 #include <sys/time.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21 #include <time.h>
22 #include <unistd.h>
23 #include <X11/Xatom.h>
24 #include <X11/Xlib.h>
25 #include <X11/Xutil.h>
26 #include <X11/cursorfont.h>
27 #include <X11/keysym.h>
28 #include <X11/extensions/Xdbe.h>
29 #include <X11/Xft/Xft.h>
30 #include <fontconfig/fontconfig.h>
31
32 #define Glyph Glyph_
33 #define Font Font_
34 #define Draw XftDraw *
35 #define Colour XftColor
36 #define Colourmap Colormap
37
38 #if   defined(__linux)
39  #include <pty.h>
40 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
41  #include <util.h>
42 #elif defined(__FreeBSD__) || defined(__DragonFly__)
43  #include <libutil.h>
44 #endif
45
46 #define USAGE \
47         "st " VERSION " (c) 2010-2013 st engineers\n" \
48         "usage: st [-v] [-c class] [-f font] [-g geometry] [-o file]" \
49         " [-t title] [-w windowid] [-e command ...]\n"
50
51 /* XEMBED messages */
52 #define XEMBED_FOCUS_IN  4
53 #define XEMBED_FOCUS_OUT 5
54
55 /* Arbitrary sizes */
56 #define UTF_SIZ       4
57 #define ESC_BUF_SIZ   (128*UTF_SIZ)
58 #define ESC_ARG_SIZ   16
59 #define STR_BUF_SIZ   ESC_BUF_SIZ
60 #define STR_ARG_SIZ   ESC_ARG_SIZ
61 #define DRAW_BUF_SIZ  20*1024
62 #define XK_ANY_MOD    UINT_MAX
63 #define XK_NO_MOD     0
64 #define XK_SWITCH_MOD (1<<13)
65
66 #define REDRAW_TIMEOUT (80*1000) /* 80 ms */
67
68 /* macros */
69 #define SERRNO strerror(errno)
70 #define MIN(a, b)  ((a) < (b) ? (a) : (b))
71 #define MAX(a, b)  ((a) < (b) ? (b) : (a))
72 #define LEN(a)     (sizeof(a) / sizeof(a[0]))
73 #define DEFAULT(a, b)     (a) = (a) ? (a) : (b)
74 #define BETWEEN(x, a, b)  ((a) <= (x) && (x) <= (b))
75 #define LIMIT(x, a, b)    (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
76 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
77 #define IS_SET(flag) ((term.mode & (flag)) != 0)
78 #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + (t1.tv_usec-t2.tv_usec)/1000)
79
80 #define VT102ID "\033[?6c"
81
82 enum glyph_attribute {
83         ATTR_NULL      = 0,
84         ATTR_REVERSE   = 1,
85         ATTR_UNDERLINE = 2,
86         ATTR_BOLD      = 4,
87         ATTR_GFX       = 8,
88         ATTR_ITALIC    = 16,
89         ATTR_BLINK     = 32,
90 };
91
92 enum cursor_movement {
93         CURSOR_SAVE,
94         CURSOR_LOAD
95 };
96
97 enum cursor_state {
98         CURSOR_DEFAULT  = 0,
99         CURSOR_WRAPNEXT = 1,
100         CURSOR_ORIGIN   = 2
101 };
102
103 enum glyph_state {
104         GLYPH_SET   = 1,
105         GLYPH_DIRTY = 2
106 };
107
108 enum term_mode {
109         MODE_WRAP        = 1,
110         MODE_INSERT      = 2,
111         MODE_APPKEYPAD   = 4,
112         MODE_ALTSCREEN   = 8,
113         MODE_CRLF        = 16,
114         MODE_MOUSEBTN    = 32,
115         MODE_MOUSEMOTION = 64,
116         MODE_MOUSE       = 32|64,
117         MODE_REVERSE     = 128,
118         MODE_KBDLOCK     = 256,
119         MODE_HIDE        = 512,
120         MODE_ECHO        = 1024,
121         MODE_APPCURSOR   = 2048,
122         MODE_MOUSESGR    = 4096,
123 };
124
125 enum escape_state {
126         ESC_START      = 1,
127         ESC_CSI = 2,
128         ESC_STR = 4, /* DSC, OSC, PM, APC */
129         ESC_ALTCHARSET = 8,
130         ESC_STR_END    = 16, /* a final string was encountered */
131         ESC_TEST       = 32, /* Enter in test mode */
132 };
133
134 enum window_state {
135         WIN_VISIBLE = 1,
136         WIN_REDRAW  = 2,
137         WIN_FOCUSED = 4
138 };
139
140 /* bit macro */
141 #undef B0
142 enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 };
143
144 typedef unsigned char uchar;
145 typedef unsigned int uint;
146 typedef unsigned long ulong;
147 typedef unsigned short ushort;
148
149 typedef struct {
150         char c[UTF_SIZ];     /* character code */
151         uchar mode;  /* attribute flags */
152         ushort fg;   /* foreground  */
153         ushort bg;   /* background  */
154         uchar state; /* state flags    */
155 } Glyph;
156
157 typedef Glyph *Line;
158
159 typedef struct {
160         Glyph attr;      /* current char attributes */
161         int x;
162         int y;
163         char state;
164 } TCursor;
165
166 /* CSI Escape sequence structs */
167 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
168 typedef struct {
169         char buf[ESC_BUF_SIZ]; /* raw string */
170         int len;               /* raw string length */
171         char priv;
172         int arg[ESC_ARG_SIZ];
173         int narg;              /* nb of args */
174         char mode;
175 } CSIEscape;
176
177 /* STR Escape sequence structs */
178 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
179 typedef struct {
180         char type;           /* ESC type ... */
181         char buf[STR_BUF_SIZ]; /* raw string */
182         int len;               /* raw string length */
183         char *args[STR_ARG_SIZ];
184         int narg;             /* nb of args */
185 } STREscape;
186
187 /* Internal representation of the screen */
188 typedef struct {
189         int row;        /* nb row */
190         int col;        /* nb col */
191         Line *line;     /* screen */
192         Line *alt;      /* alternate screen */
193         bool *dirty;    /* dirtyness of lines */
194         TCursor c;      /* cursor */
195         int top;        /* top    scroll limit */
196         int bot;        /* bottom scroll limit */
197         int mode;       /* terminal mode flags */
198         int esc;        /* escape state flags */
199         bool numlock;   /* lock numbers in keyboard */
200         bool *tabs;
201 } Term;
202
203 /* Purely graphic info */
204 typedef struct {
205         Display *dpy;
206         Colourmap cmap;
207         Window win;
208         Drawable buf;
209         Atom xembed, wmdeletewin;
210         XIM xim;
211         XIC xic;
212         Draw draw;
213         Visual *vis;
214         int scr;
215         bool isfixed; /* is fixed geometry? */
216         int fx, fy, fw, fh; /* fixed geometry */
217         int tw, th; /* tty width and height */
218         int w, h; /* window width and height */
219         int ch; /* char height */
220         int cw; /* char width  */
221         char state; /* focus, redraw, visible */
222 } XWindow;
223
224 typedef struct {
225         KeySym k;
226         uint mask;
227         char s[ESC_BUF_SIZ];
228         /* three valued logic variables: 0 indifferent, 1 on, -1 off */
229         signed char appkey;             /* application keypad */
230         signed char appcursor;          /* application cursor */
231         signed char crlf;               /* crlf mode          */
232 } Key;
233
234 /* TODO: use better name for vars... */
235 typedef struct {
236         int mode;
237         int bx, by;
238         int ex, ey;
239         struct {
240                 int x, y;
241         } b, e;
242         char *clip;
243         Atom xtarget;
244         bool alt;
245         struct timeval tclick1;
246         struct timeval tclick2;
247 } Selection;
248
249 typedef union {
250         int i;
251         unsigned int ui;
252         float f;
253         const void *v;
254 } Arg;
255
256 typedef struct {
257         unsigned int mod;
258         KeySym keysym;
259         void (*func)(const Arg *);
260         const Arg arg;
261 } Shortcut;
262
263 /* function definitions used in config.h */
264 static void xzoom(const Arg *);
265 static void selpaste(const Arg *);
266 static void numlock(const Arg *);
267
268 /* Config.h for applying patches and the configuration. */
269 #include "config.h"
270
271 /* Font structure */
272 typedef struct {
273         int height;
274         int width;
275         int ascent;
276         int descent;
277         short lbearing;
278         short rbearing;
279         XftFont *match;
280         FcFontSet *set;
281         FcPattern *pattern;
282 } Font;
283
284 /* Drawing Context */
285 typedef struct {
286         Colour col[LEN(colorname) < 256 ? 256 : LEN(colorname)];
287         Font font, bfont, ifont, ibfont;
288         GC gc;
289 } DC;
290
291 static void die(const char *, ...);
292 static void draw(void);
293 static void redraw(int);
294 static void drawregion(int, int, int, int);
295 static void execsh(void);
296 static void sigchld(int);
297 static void run(void);
298
299 static void csidump(void);
300 static void csihandle(void);
301 static void csiparse(void);
302 static void csireset(void);
303 static void strdump(void);
304 static void strhandle(void);
305 static void strparse(void);
306 static void strreset(void);
307
308 static void tclearregion(int, int, int, int, int);
309 static void tcursor(int);
310 static void tdeletechar(int);
311 static void tdeleteline(int);
312 static void tinsertblank(int);
313 static void tinsertblankline(int);
314 static void tmoveto(int, int);
315 static void tmoveato(int x, int y);
316 static void tnew(int, int);
317 static void tnewline(int);
318 static void tputtab(bool);
319 static void tputc(char *, int);
320 static void treset(void);
321 static int tresize(int, int);
322 static void tscrollup(int, int);
323 static void tscrolldown(int, int);
324 static void tsetattr(int*, int);
325 static void tsetchar(char *, Glyph *, int, int);
326 static void tsetscroll(int, int);
327 static void tswapscreen(void);
328 static void tsetdirt(int, int);
329 static void tsetmode(bool, bool, int *, int);
330 static void tfulldirt(void);
331 static void techo(char *, int);
332
333 static inline bool match(uint, uint);
334 static void ttynew(void);
335 static void ttyread(void);
336 static void ttyresize(void);
337 static void ttywrite(const char *, size_t);
338
339 static void xdraws(char *, Glyph, int, int, int, int);
340 static void xhints(void);
341 static void xclear(int, int, int, int);
342 static void xdrawcursor(void);
343 static void xinit(void);
344 static void xloadcols(void);
345 static int xloadfont(Font *, FcPattern *);
346 static void xloadfonts(char *, int);
347 static void xresettitle(void);
348 static void xseturgency(int);
349 static void xsetsel(char*);
350 static void xtermclear(int, int, int, int);
351 static void xunloadfonts(void);
352 static void xresize(int, int);
353
354 static void expose(XEvent *);
355 static void visibility(XEvent *);
356 static void unmap(XEvent *);
357 static char *kmap(KeySym, uint);
358 static void kpress(XEvent *);
359 static void cmessage(XEvent *);
360 static void cresize(int, int);
361 static void resize(XEvent *);
362 static void focus(XEvent *);
363 static void brelease(XEvent *);
364 static void bpress(XEvent *);
365 static void bmotion(XEvent *);
366 static void selnotify(XEvent *);
367 static void selclear(XEvent *);
368 static void selrequest(XEvent *);
369
370 static void selinit(void);
371 static inline bool selected(int, int);
372 static void selcopy(void);
373 static void selscroll(int, int);
374
375 static int utf8decode(char *, long *);
376 static int utf8encode(long *, char *);
377 static int utf8size(char *);
378 static int isfullutf8(char *, int);
379
380 static ssize_t xwrite(int, char *, size_t);
381 static void *xmalloc(size_t);
382 static void *xrealloc(void *, size_t);
383 static void *xcalloc(size_t, size_t);
384
385 static void (*handler[LASTEvent])(XEvent *) = {
386         [KeyPress] = kpress,
387         [ClientMessage] = cmessage,
388         [ConfigureNotify] = resize,
389         [VisibilityNotify] = visibility,
390         [UnmapNotify] = unmap,
391         [Expose] = expose,
392         [FocusIn] = focus,
393         [FocusOut] = focus,
394         [MotionNotify] = bmotion,
395         [ButtonPress] = bpress,
396         [ButtonRelease] = brelease,
397         [SelectionClear] = selclear,
398         [SelectionNotify] = selnotify,
399         [SelectionRequest] = selrequest,
400 };
401
402 /* Globals */
403 static DC dc;
404 static XWindow xw;
405 static Term term;
406 static CSIEscape csiescseq;
407 static STREscape strescseq;
408 static int cmdfd;
409 static pid_t pid;
410 static Selection sel;
411 static int iofd = -1;
412 static char **opt_cmd = NULL;
413 static char *opt_io = NULL;
414 static char *opt_title = NULL;
415 static char *opt_embed = NULL;
416 static char *opt_class = NULL;
417 static char *opt_font = NULL;
418
419 bool usedbe = False;
420
421 static char *usedfont = NULL;
422 static int usedfontsize = 0;
423
424 /* Font Ring Cache */
425 enum {
426         FRC_NORMAL,
427         FRC_ITALIC,
428         FRC_BOLD,
429         FRC_ITALICBOLD
430 };
431
432 typedef struct {
433         XftFont *font;
434         long c;
435         int flags;
436 } Fontcache;
437
438 /*
439  * Fontcache is a ring buffer, with frccur as current position and frclen as
440  * the current length of used elements.
441  */
442
443 static Fontcache frc[1024];
444 static int frccur = -1, frclen = 0;
445
446 ssize_t
447 xwrite(int fd, char *s, size_t len) {
448         size_t aux = len;
449
450         while(len > 0) {
451                 ssize_t r = write(fd, s, len);
452                 if(r < 0)
453                         return r;
454                 len -= r;
455                 s += r;
456         }
457         return aux;
458 }
459
460 void *
461 xmalloc(size_t len) {
462         void *p = malloc(len);
463
464         if(!p)
465                 die("Out of memory\n");
466
467         return p;
468 }
469
470 void *
471 xrealloc(void *p, size_t len) {
472         if((p = realloc(p, len)) == NULL)
473                 die("Out of memory\n");
474
475         return p;
476 }
477
478 void *
479 xcalloc(size_t nmemb, size_t size) {
480         void *p = calloc(nmemb, size);
481
482         if(!p)
483                 die("Out of memory\n");
484
485         return p;
486 }
487
488 int
489 utf8decode(char *s, long *u) {
490         uchar c;
491         int i, n, rtn;
492
493         rtn = 1;
494         c = *s;
495         if(~c & B7) { /* 0xxxxxxx */
496                 *u = c;
497                 return rtn;
498         } else if((c & (B7|B6|B5)) == (B7|B6)) { /* 110xxxxx */
499                 *u = c&(B4|B3|B2|B1|B0);
500                 n = 1;
501         } else if((c & (B7|B6|B5|B4)) == (B7|B6|B5)) { /* 1110xxxx */
502                 *u = c&(B3|B2|B1|B0);
503                 n = 2;
504         } else if((c & (B7|B6|B5|B4|B3)) == (B7|B6|B5|B4)) { /* 11110xxx */
505                 *u = c & (B2|B1|B0);
506                 n = 3;
507         } else {
508                 goto invalid;
509         }
510
511         for(i = n, ++s; i > 0; --i, ++rtn, ++s) {
512                 c = *s;
513                 if((c & (B7|B6)) != B7) /* 10xxxxxx */
514                         goto invalid;
515                 *u <<= 6;
516                 *u |= c & (B5|B4|B3|B2|B1|B0);
517         }
518
519         if((n == 1 && *u < 0x80) ||
520            (n == 2 && *u < 0x800) ||
521            (n == 3 && *u < 0x10000) ||
522            (*u >= 0xD800 && *u <= 0xDFFF)) {
523                 goto invalid;
524         }
525
526         return rtn;
527 invalid:
528         *u = 0xFFFD;
529
530         return rtn;
531 }
532
533 int
534 utf8encode(long *u, char *s) {
535         uchar *sp;
536         ulong uc;
537         int i, n;
538
539         sp = (uchar *)s;
540         uc = *u;
541         if(uc < 0x80) {
542                 *sp = uc; /* 0xxxxxxx */
543                 return 1;
544         } else if(*u < 0x800) {
545                 *sp = (uc >> 6) | (B7|B6); /* 110xxxxx */
546                 n = 1;
547         } else if(uc < 0x10000) {
548                 *sp = (uc >> 12) | (B7|B6|B5); /* 1110xxxx */
549                 n = 2;
550         } else if(uc <= 0x10FFFF) {
551                 *sp = (uc >> 18) | (B7|B6|B5|B4); /* 11110xxx */
552                 n = 3;
553         } else {
554                 goto invalid;
555         }
556
557         for(i=n,++sp; i>0; --i,++sp)
558                 *sp = ((uc >> 6*(i-1)) & (B5|B4|B3|B2|B1|B0)) | B7; /* 10xxxxxx */
559
560         return n+1;
561 invalid:
562         /* U+FFFD */
563         *s++ = '\xEF';
564         *s++ = '\xBF';
565         *s = '\xBD';
566
567         return 3;
568 }
569
570 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
571    UTF-8 otherwise return 0 */
572 int
573 isfullutf8(char *s, int b) {
574         uchar *c1, *c2, *c3;
575
576         c1 = (uchar *)s;
577         c2 = (uchar *)++s;
578         c3 = (uchar *)++s;
579         if(b < 1) {
580                 return 0;
581         } else if((*c1&(B7|B6|B5)) == (B7|B6) && b == 1) {
582                 return 0;
583         } else if((*c1&(B7|B6|B5|B4)) == (B7|B6|B5) &&
584             ((b == 1) ||
585             ((b == 2) && (*c2&(B7|B6)) == B7))) {
586                 return 0;
587         } else if((*c1&(B7|B6|B5|B4|B3)) == (B7|B6|B5|B4) &&
588             ((b == 1) ||
589             ((b == 2) && (*c2&(B7|B6)) == B7) ||
590             ((b == 3) && (*c2&(B7|B6)) == B7 && (*c3&(B7|B6)) == B7))) {
591                 return 0;
592         } else {
593                 return 1;
594         }
595 }
596
597 int
598 utf8size(char *s) {
599         uchar c = *s;
600
601         if(~c&B7) {
602                 return 1;
603         } else if((c&(B7|B6|B5)) == (B7|B6)) {
604                 return 2;
605         } else if((c&(B7|B6|B5|B4)) == (B7|B6|B5)) {
606                 return 3;
607         } else {
608                 return 4;
609         }
610 }
611
612 void
613 selinit(void) {
614         memset(&sel.tclick1, 0, sizeof(sel.tclick1));
615         memset(&sel.tclick2, 0, sizeof(sel.tclick2));
616         sel.mode = 0;
617         sel.bx = -1;
618         sel.clip = NULL;
619         sel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
620         if(sel.xtarget == None)
621                 sel.xtarget = XA_STRING;
622 }
623
624 static int
625 x2col(int x) {
626         x -= borderpx;
627         x /= xw.cw;
628
629         return LIMIT(x, 0, term.col-1);
630 }
631
632 static int
633 y2row(int y) {
634         y -= borderpx;
635         y /= xw.ch;
636
637         return LIMIT(y, 0, term.row-1);
638 }
639
640 static inline bool
641 selected(int x, int y) {
642         int bx, ex;
643
644         if(sel.ey == y && sel.by == y) {
645                 bx = MIN(sel.bx, sel.ex);
646                 ex = MAX(sel.bx, sel.ex);
647                 return BETWEEN(x, bx, ex);
648         }
649
650         return ((sel.b.y < y && y < sel.e.y)
651                         || (y == sel.e.y && x <= sel.e.x))
652                         || (y == sel.b.y && x >= sel.b.x
653                                 && (x <= sel.e.x || sel.b.y != sel.e.y));
654 }
655
656 void
657 getbuttoninfo(XEvent *e) {
658         sel.alt = IS_SET(MODE_ALTSCREEN);
659
660         sel.ex = x2col(e->xbutton.x);
661         sel.ey = y2row(e->xbutton.y);
662
663         sel.b.x = sel.by < sel.ey ? sel.bx : sel.ex;
664         sel.b.y = MIN(sel.by, sel.ey);
665         sel.e.x = sel.by < sel.ey ? sel.ex : sel.bx;
666         sel.e.y = MAX(sel.by, sel.ey);
667 }
668
669 void
670 mousereport(XEvent *e) {
671         int x = x2col(e->xbutton.x), y = y2row(e->xbutton.y),
672             button = e->xbutton.button, state = e->xbutton.state,
673             len;
674         char buf[40];
675         static int ob, ox, oy;
676
677         /* from urxvt */
678         if(e->xbutton.type == MotionNotify) {
679                 if(!IS_SET(MODE_MOUSEMOTION) || (x == ox && y == oy))
680                         return;
681                 button = ob + 32;
682                 ox = x, oy = y;
683         } else if(!IS_SET(MODE_MOUSESGR)
684                         && (e->xbutton.type == ButtonRelease
685                                 || button == AnyButton)) {
686                 button = 3;
687         } else {
688                 button -= Button1;
689                 if(button >= 3)
690                         button += 64 - 3;
691                 if(e->xbutton.type == ButtonPress) {
692                         ob = button;
693                         ox = x, oy = y;
694                 }
695         }
696
697         button += (state & ShiftMask   ? 4  : 0)
698                 + (state & Mod4Mask    ? 8  : 0)
699                 + (state & ControlMask ? 16 : 0);
700
701         len = 0;
702         if(IS_SET(MODE_MOUSESGR)) {
703                 len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
704                                 button, x+1, y+1,
705                                 e->xbutton.type == ButtonRelease ? 'm' : 'M');
706         } else if(x < 223 && y < 223) {
707                 len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
708                                 32+button, 32+x+1, 32+y+1);
709         } else {
710                 return;
711         }
712
713         ttywrite(buf, len);
714 }
715
716 void
717 bpress(XEvent *e) {
718         if(IS_SET(MODE_MOUSE)) {
719                 mousereport(e);
720         } else if(e->xbutton.button == Button1) {
721                 if(sel.bx != -1) {
722                         sel.bx = -1;
723                         tsetdirt(sel.b.y, sel.e.y);
724                         draw();
725                 }
726                 sel.mode = 1;
727                 sel.ex = sel.bx = x2col(e->xbutton.x);
728                 sel.ey = sel.by = y2row(e->xbutton.y);
729         } else if(e->xbutton.button == Button4) {
730                 ttywrite("\031", 1);
731         } else if(e->xbutton.button == Button5) {
732                 ttywrite("\005", 1);
733         }
734 }
735
736 void
737 selcopy(void) {
738         char *str, *ptr, *p;
739         int x, y, bufsize, is_selected = 0, size;
740         Glyph *gp, *last;
741
742         if(sel.bx == -1) {
743                 str = NULL;
744         } else {
745                 bufsize = (term.col+1) * (sel.e.y-sel.b.y+1) * UTF_SIZ;
746                 ptr = str = xmalloc(bufsize);
747
748                 /* append every set & selected glyph to the selection */
749                 for(y = 0; y < term.row; y++) {
750                         gp = &term.line[y][0];
751                         last = gp + term.col;
752
753                         while(--last >= gp && !(last->state & GLYPH_SET))
754                                 /* nothing */;
755
756                         for(x = 0; gp <= last; x++, ++gp) {
757                                 if(!(is_selected = selected(x, y)))
758                                         continue;
759
760                                 p = (gp->state & GLYPH_SET) ? gp->c : " ";
761                                 size = utf8size(p);
762                                 memcpy(ptr, p, size);
763                                 ptr += size;
764                         }
765                         /* \n at the end of every selected line except for the last one */
766                         if(is_selected && y < sel.e.y)
767                                 *ptr++ = '\n';
768                 }
769                 *ptr = 0;
770         }
771         xsetsel(str);
772 }
773
774 void
775 selnotify(XEvent *e) {
776         ulong nitems, ofs, rem;
777         int format;
778         uchar *data;
779         Atom type;
780
781         ofs = 0;
782         do {
783                 if(XGetWindowProperty(xw.dpy, xw.win, XA_PRIMARY, ofs, BUFSIZ/4,
784                                         False, AnyPropertyType, &type, &format,
785                                         &nitems, &rem, &data)) {
786                         fprintf(stderr, "Clipboard allocation failed\n");
787                         return;
788                 }
789                 ttywrite((const char *) data, nitems * format / 8);
790                 XFree(data);
791                 /* number of 32-bit chunks returned */
792                 ofs += nitems * format / 32;
793         } while(rem > 0);
794 }
795
796 void
797 selpaste(const Arg *dummy) {
798         XConvertSelection(xw.dpy, XA_PRIMARY, sel.xtarget, XA_PRIMARY,
799                         xw.win, CurrentTime);
800 }
801
802 void selclear(XEvent *e) {
803         if(sel.bx == -1)
804                 return;
805         sel.bx = -1;
806         tsetdirt(sel.b.y, sel.e.y);
807 }
808
809 void
810 selrequest(XEvent *e) {
811         XSelectionRequestEvent *xsre;
812         XSelectionEvent xev;
813         Atom xa_targets, string;
814
815         xsre = (XSelectionRequestEvent *) e;
816         xev.type = SelectionNotify;
817         xev.requestor = xsre->requestor;
818         xev.selection = xsre->selection;
819         xev.target = xsre->target;
820         xev.time = xsre->time;
821         /* reject */
822         xev.property = None;
823
824         xa_targets = XInternAtom(xw.dpy, "TARGETS", 0);
825         if(xsre->target == xa_targets) {
826                 /* respond with the supported type */
827                 string = sel.xtarget;
828                 XChangeProperty(xsre->display, xsre->requestor, xsre->property,
829                                 XA_ATOM, 32, PropModeReplace,
830                                 (uchar *) &string, 1);
831                 xev.property = xsre->property;
832         } else if(xsre->target == sel.xtarget && sel.clip != NULL) {
833                 XChangeProperty(xsre->display, xsre->requestor, xsre->property,
834                                 xsre->target, 8, PropModeReplace,
835                                 (uchar *) sel.clip, strlen(sel.clip));
836                 xev.property = xsre->property;
837         }
838
839         /* all done, send a notification to the listener */
840         if(!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *) &xev))
841                 fprintf(stderr, "Error sending SelectionNotify event\n");
842 }
843
844 void
845 xsetsel(char *str) {
846         /* register the selection for both the clipboard and the primary */
847         Atom clipboard;
848
849         free(sel.clip);
850         sel.clip = str;
851
852         XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
853
854         clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
855         XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);
856 }
857
858 void
859 brelease(XEvent *e) {
860         struct timeval now;
861
862         if(IS_SET(MODE_MOUSE)) {
863                 mousereport(e);
864                 return;
865         }
866
867         if(e->xbutton.button == Button2) {
868                 selpaste(NULL);
869         } else if(e->xbutton.button == Button1) {
870                 sel.mode = 0;
871                 getbuttoninfo(e);
872                 term.dirty[sel.ey] = 1;
873                 if(sel.bx == sel.ex && sel.by == sel.ey) {
874                         sel.bx = -1;
875                         gettimeofday(&now, NULL);
876
877                         if(TIMEDIFF(now, sel.tclick2) <= tripleclicktimeout) {
878                                 /* triple click on the line */
879                                 sel.b.x = sel.bx = 0;
880                                 sel.e.x = sel.ex = term.col;
881                                 sel.b.y = sel.e.y = sel.ey;
882                                 selcopy();
883                         } else if(TIMEDIFF(now, sel.tclick1) <= doubleclicktimeout) {
884                                 /* double click to select word */
885                                 sel.bx = sel.ex;
886                                 while(sel.bx > 0 && term.line[sel.ey][sel.bx-1].state & GLYPH_SET &&
887                                                 term.line[sel.ey][sel.bx-1].c[0] != ' ') {
888                                         sel.bx--;
889                                 }
890                                 sel.b.x = sel.bx;
891                                 while(sel.ex < term.col-1 && term.line[sel.ey][sel.ex+1].state & GLYPH_SET &&
892                                                 term.line[sel.ey][sel.ex+1].c[0] != ' ') {
893                                         sel.ex++;
894                                 }
895                                 sel.e.x = sel.ex;
896                                 sel.b.y = sel.e.y = sel.ey;
897                                 selcopy();
898                         }
899                 } else {
900                         selcopy();
901                 }
902         }
903
904         memcpy(&sel.tclick2, &sel.tclick1, sizeof(struct timeval));
905         gettimeofday(&sel.tclick1, NULL);
906 }
907
908 void
909 bmotion(XEvent *e) {
910         int starty, endy, oldey, oldex;
911
912         if(IS_SET(MODE_MOUSE)) {
913                 mousereport(e);
914                 return;
915         }
916
917         if(!sel.mode)
918                 return;
919
920         oldey = sel.ey;
921         oldex = sel.ex;
922         getbuttoninfo(e);
923
924         if(oldey != sel.ey || oldex != sel.ex) {
925                 starty = MIN(oldey, sel.ey);
926                 endy = MAX(oldey, sel.ey);
927                 tsetdirt(starty, endy);
928         }
929 }
930
931 void
932 die(const char *errstr, ...) {
933         va_list ap;
934
935         va_start(ap, errstr);
936         vfprintf(stderr, errstr, ap);
937         va_end(ap);
938         exit(EXIT_FAILURE);
939 }
940
941 void
942 execsh(void) {
943         char **args;
944         char *envshell = getenv("SHELL");
945         const struct passwd *pass = getpwuid(getuid());
946         char buf[sizeof(long) * 8 + 1];
947
948         unsetenv("COLUMNS");
949         unsetenv("LINES");
950         unsetenv("TERMCAP");
951
952         if(pass) {
953                 setenv("LOGNAME", pass->pw_name, 1);
954                 setenv("USER", pass->pw_name, 1);
955                 setenv("SHELL", pass->pw_shell, 0);
956                 setenv("HOME", pass->pw_dir, 0);
957         }
958
959         snprintf(buf, sizeof(buf), "%lu", xw.win);
960         setenv("WINDOWID", buf, 1);
961
962         signal(SIGCHLD, SIG_DFL);
963         signal(SIGHUP, SIG_DFL);
964         signal(SIGINT, SIG_DFL);
965         signal(SIGQUIT, SIG_DFL);
966         signal(SIGTERM, SIG_DFL);
967         signal(SIGALRM, SIG_DFL);
968
969         DEFAULT(envshell, shell);
970         setenv("TERM", termname, 1);
971         args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL};
972         execvp(args[0], args);
973         exit(EXIT_FAILURE);
974 }
975
976 void
977 sigchld(int a) {
978         int stat = 0;
979
980         if(waitpid(pid, &stat, 0) < 0)
981                 die("Waiting for pid %hd failed: %s\n", pid, SERRNO);
982
983         if(WIFEXITED(stat)) {
984                 exit(WEXITSTATUS(stat));
985         } else {
986                 exit(EXIT_FAILURE);
987         }
988 }
989
990 void
991 ttynew(void) {
992         int m, s;
993         struct winsize w = {term.row, term.col, 0, 0};
994
995         /* seems to work fine on linux, openbsd and freebsd */
996         if(openpty(&m, &s, NULL, NULL, &w) < 0)
997                 die("openpty failed: %s\n", SERRNO);
998
999         switch(pid = fork()) {
1000         case -1:
1001                 die("fork failed\n");
1002                 break;
1003         case 0:
1004                 setsid(); /* create a new process group */
1005                 dup2(s, STDIN_FILENO);
1006                 dup2(s, STDOUT_FILENO);
1007                 dup2(s, STDERR_FILENO);
1008                 if(ioctl(s, TIOCSCTTY, NULL) < 0)
1009                         die("ioctl TIOCSCTTY failed: %s\n", SERRNO);
1010                 close(s);
1011                 close(m);
1012                 execsh();
1013                 break;
1014         default:
1015                 close(s);
1016                 cmdfd = m;
1017                 signal(SIGCHLD, sigchld);
1018                 if(opt_io) {
1019                         iofd = (!strcmp(opt_io, "-")) ?
1020                                   STDOUT_FILENO :
1021                                   open(opt_io, O_WRONLY | O_CREAT, 0666);
1022                         if(iofd < 0) {
1023                                 fprintf(stderr, "Error opening %s:%s\n",
1024                                         opt_io, strerror(errno));
1025                         }
1026                 }
1027         }
1028 }
1029
1030 void
1031 dump(char c) {
1032         static int col;
1033
1034         fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
1035         if(++col % 10 == 0)
1036                 fprintf(stderr, "\n");
1037 }
1038
1039 void
1040 ttyread(void) {
1041         static char buf[BUFSIZ];
1042         static int buflen = 0;
1043         char *ptr;
1044         char s[UTF_SIZ];
1045         int charsize; /* size of utf8 char in bytes */
1046         long utf8c;
1047         int ret;
1048
1049         /* append read bytes to unprocessed bytes */
1050         if((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
1051                 die("Couldn't read from shell: %s\n", SERRNO);
1052
1053         /* process every complete utf8 char */
1054         buflen += ret;
1055         ptr = buf;
1056         while(buflen >= UTF_SIZ || isfullutf8(ptr,buflen)) {
1057                 charsize = utf8decode(ptr, &utf8c);
1058                 utf8encode(&utf8c, s);
1059                 tputc(s, charsize);
1060                 ptr += charsize;
1061                 buflen -= charsize;
1062         }
1063
1064         /* keep any uncomplete utf8 char for the next call */
1065         memmove(buf, ptr, buflen);
1066 }
1067
1068 void
1069 ttywrite(const char *s, size_t n) {
1070         if(write(cmdfd, s, n) == -1)
1071                 die("write error on tty: %s\n", SERRNO);
1072 }
1073
1074 void
1075 ttyresize(void) {
1076         struct winsize w;
1077
1078         w.ws_row = term.row;
1079         w.ws_col = term.col;
1080         w.ws_xpixel = xw.tw;
1081         w.ws_ypixel = xw.th;
1082         if(ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
1083                 fprintf(stderr, "Couldn't set window size: %s\n", SERRNO);
1084 }
1085
1086 void
1087 tsetdirt(int top, int bot) {
1088         int i;
1089
1090         LIMIT(top, 0, term.row-1);
1091         LIMIT(bot, 0, term.row-1);
1092
1093         for(i = top; i <= bot; i++)
1094                 term.dirty[i] = 1;
1095 }
1096
1097 void
1098 tfulldirt(void) {
1099         tsetdirt(0, term.row-1);
1100 }
1101
1102 void
1103 tcursor(int mode) {
1104         static TCursor c;
1105
1106         if(mode == CURSOR_SAVE) {
1107                 c = term.c;
1108         } else if(mode == CURSOR_LOAD) {
1109                 term.c = c;
1110                 tmoveto(c.x, c.y);
1111         }
1112 }
1113
1114 void
1115 treset(void) {
1116         uint i;
1117
1118         term.c = (TCursor){{
1119                 .mode = ATTR_NULL,
1120                 .fg = defaultfg,
1121                 .bg = defaultbg
1122         }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1123
1124         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1125         for(i = tabspaces; i < term.col; i += tabspaces)
1126                 term.tabs[i] = 1;
1127         term.top = 0;
1128         term.bot = term.row - 1;
1129         term.mode = MODE_WRAP;
1130
1131         tclearregion(0, 0, term.col-1, term.row-1, 0);
1132         tmoveto(0, 0);
1133         tcursor(CURSOR_SAVE);
1134 }
1135
1136 void
1137 tnew(int col, int row) {
1138         /* set screen size */
1139         term.row = row;
1140         term.col = col;
1141         term.line = xmalloc(term.row * sizeof(Line));
1142         term.alt  = xmalloc(term.row * sizeof(Line));
1143         term.dirty = xmalloc(term.row * sizeof(*term.dirty));
1144         term.tabs = xmalloc(term.col * sizeof(*term.tabs));
1145
1146         for(row = 0; row < term.row; row++) {
1147                 term.line[row] = xmalloc(term.col * sizeof(Glyph));
1148                 term.alt [row] = xmalloc(term.col * sizeof(Glyph));
1149                 term.dirty[row] = 0;
1150         }
1151
1152         term.numlock = 1;
1153         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1154         /* setup screen */
1155         treset();
1156 }
1157
1158 void
1159 tswapscreen(void) {
1160         Line *tmp = term.line;
1161
1162         term.line = term.alt;
1163         term.alt = tmp;
1164         term.mode ^= MODE_ALTSCREEN;
1165         tfulldirt();
1166 }
1167
1168 void
1169 tscrolldown(int orig, int n) {
1170         int i;
1171         Line temp;
1172
1173         LIMIT(n, 0, term.bot-orig+1);
1174
1175         tclearregion(0, term.bot-n+1, term.col-1, term.bot, 0);
1176
1177         for(i = term.bot; i >= orig+n; i--) {
1178                 temp = term.line[i];
1179                 term.line[i] = term.line[i-n];
1180                 term.line[i-n] = temp;
1181
1182                 term.dirty[i] = 1;
1183                 term.dirty[i-n] = 1;
1184         }
1185
1186         selscroll(orig, n);
1187 }
1188
1189 void
1190 tscrollup(int orig, int n) {
1191         int i;
1192         Line temp;
1193         LIMIT(n, 0, term.bot-orig+1);
1194
1195         tclearregion(0, orig, term.col-1, orig+n-1, 0);
1196
1197         for(i = orig; i <= term.bot-n; i++) {
1198                  temp = term.line[i];
1199                  term.line[i] = term.line[i+n];
1200                  term.line[i+n] = temp;
1201
1202                  term.dirty[i] = 1;
1203                  term.dirty[i+n] = 1;
1204         }
1205
1206         selscroll(orig, -n);
1207 }
1208
1209 void
1210 selscroll(int orig, int n) {
1211         if(sel.bx == -1)
1212                 return;
1213
1214         if(BETWEEN(sel.by, orig, term.bot) || BETWEEN(sel.ey, orig, term.bot)) {
1215                 if((sel.by += n) > term.bot || (sel.ey += n) < term.top) {
1216                         sel.bx = -1;
1217                         return;
1218                 }
1219                 if(sel.by < term.top) {
1220                         sel.by = term.top;
1221                         sel.bx = 0;
1222                 }
1223                 if(sel.ey > term.bot) {
1224                         sel.ey = term.bot;
1225                         sel.ex = term.col;
1226                 }
1227                 sel.b.y = sel.by, sel.b.x = sel.bx;
1228                 sel.e.y = sel.ey, sel.e.x = sel.ex;
1229         }
1230 }
1231
1232 void
1233 tnewline(int first_col) {
1234         int y = term.c.y;
1235
1236         if(y == term.bot) {
1237                 tscrollup(term.top, 1);
1238         } else {
1239                 y++;
1240         }
1241         tmoveto(first_col ? 0 : term.c.x, y);
1242 }
1243
1244 void
1245 csiparse(void) {
1246         /* int noarg = 1; */
1247         char *p = csiescseq.buf;
1248
1249         csiescseq.narg = 0;
1250         if(*p == '?')
1251                 csiescseq.priv = 1, p++;
1252
1253         while(p < csiescseq.buf+csiescseq.len) {
1254                 while(isdigit(*p)) {
1255                         csiescseq.arg[csiescseq.narg] *= 10;
1256                         csiescseq.arg[csiescseq.narg] += *p++ - '0'/*, noarg = 0 */;
1257                 }
1258                 if(*p == ';' && csiescseq.narg+1 < ESC_ARG_SIZ) {
1259                         csiescseq.narg++, p++;
1260                 } else {
1261                         csiescseq.mode = *p;
1262                         csiescseq.narg++;
1263
1264                         return;
1265                 }
1266         }
1267 }
1268
1269 /* for absolute user moves, when decom is set */
1270 void
1271 tmoveato(int x, int y) {
1272         tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1273 }
1274
1275 void
1276 tmoveto(int x, int y) {
1277         int miny, maxy;
1278
1279         if(term.c.state & CURSOR_ORIGIN) {
1280                 miny = term.top;
1281                 maxy = term.bot;
1282         } else {
1283                 miny = 0;
1284                 maxy = term.row - 1;
1285         }
1286         LIMIT(x, 0, term.col-1);
1287         LIMIT(y, miny, maxy);
1288         term.c.state &= ~CURSOR_WRAPNEXT;
1289         term.c.x = x;
1290         term.c.y = y;
1291 }
1292
1293 void
1294 tsetchar(char *c, Glyph *attr, int x, int y) {
1295         static char *vt100_0[62] = { /* 0x41 - 0x7e */
1296                 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1297                 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1298                 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1299                 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1300                 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1301                 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1302                 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1303                 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1304         };
1305
1306         /*
1307          * The table is proudly stolen from rxvt.
1308          */
1309         if(attr->mode & ATTR_GFX) {
1310                 if(c[0] >= 0x41 && c[0] <= 0x7e
1311                                 && vt100_0[c[0] - 0x41]) {
1312                         c = vt100_0[c[0] - 0x41];
1313                 }
1314         }
1315
1316         term.dirty[y] = 1;
1317         term.line[y][x] = *attr;
1318         memcpy(term.line[y][x].c, c, UTF_SIZ);
1319         term.line[y][x].state |= GLYPH_SET;
1320 }
1321
1322 void
1323 tclearregion(int x1, int y1, int x2, int y2, int bce) {
1324         int x, y, temp;
1325
1326         if(x1 > x2)
1327                 temp = x1, x1 = x2, x2 = temp;
1328         if(y1 > y2)
1329                 temp = y1, y1 = y2, y2 = temp;
1330
1331         LIMIT(x1, 0, term.col-1);
1332         LIMIT(x2, 0, term.col-1);
1333         LIMIT(y1, 0, term.row-1);
1334         LIMIT(y2, 0, term.row-1);
1335
1336         for(y = y1; y <= y2; y++) {
1337                 term.dirty[y] = 1;
1338                 for(x = x1; x <= x2; x++) {
1339                         if(bce) {
1340                                 term.line[y][x] = term.c.attr;
1341                                 memcpy(term.line[y][x].c, " ", 2);
1342                                 term.line[y][x].state |= GLYPH_SET;
1343                         } else {
1344                                 term.line[y][x].state = 0;
1345                         }
1346                 }
1347         }
1348 }
1349
1350 void
1351 tdeletechar(int n) {
1352         int src = term.c.x + n;
1353         int dst = term.c.x;
1354         int size = term.col - src;
1355
1356         term.dirty[term.c.y] = 1;
1357
1358         if(src >= term.col) {
1359                 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 0);
1360                 return;
1361         }
1362
1363         memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src],
1364                         size * sizeof(Glyph));
1365         tclearregion(term.col-n, term.c.y, term.col-1, term.c.y, 0);
1366 }
1367
1368 void
1369 tinsertblank(int n) {
1370         int src = term.c.x;
1371         int dst = src + n;
1372         int size = term.col - dst;
1373
1374         term.dirty[term.c.y] = 1;
1375
1376         if(dst >= term.col) {
1377                 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 0);
1378                 return;
1379         }
1380
1381         memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src],
1382                         size * sizeof(Glyph));
1383         tclearregion(src, term.c.y, dst - 1, term.c.y, 0);
1384 }
1385
1386 void
1387 tinsertblankline(int n) {
1388         if(term.c.y < term.top || term.c.y > term.bot)
1389                 return;
1390
1391         tscrolldown(term.c.y, n);
1392 }
1393
1394 void
1395 tdeleteline(int n) {
1396         if(term.c.y < term.top || term.c.y > term.bot)
1397                 return;
1398
1399         tscrollup(term.c.y, n);
1400 }
1401
1402 void
1403 tsetattr(int *attr, int l) {
1404         int i;
1405
1406         for(i = 0; i < l; i++) {
1407                 switch(attr[i]) {
1408                 case 0:
1409                         term.c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD \
1410                                         | ATTR_ITALIC | ATTR_BLINK);
1411                         term.c.attr.fg = defaultfg;
1412                         term.c.attr.bg = defaultbg;
1413                         break;
1414                 case 1:
1415                         term.c.attr.mode |= ATTR_BOLD;
1416                         break;
1417                 case 3:
1418                         term.c.attr.mode |= ATTR_ITALIC;
1419                         break;
1420                 case 4:
1421                         term.c.attr.mode |= ATTR_UNDERLINE;
1422                         break;
1423                 case 5: /* slow blink */
1424                 case 6: /* rapid blink */
1425                         term.c.attr.mode |= ATTR_BLINK;
1426                         break;
1427                 case 7:
1428                         term.c.attr.mode |= ATTR_REVERSE;
1429                         break;
1430                 case 21:
1431                 case 22:
1432                         term.c.attr.mode &= ~ATTR_BOLD;
1433                         break;
1434                 case 23:
1435                         term.c.attr.mode &= ~ATTR_ITALIC;
1436                         break;
1437                 case 24:
1438                         term.c.attr.mode &= ~ATTR_UNDERLINE;
1439                         break;
1440                 case 25:
1441                 case 26:
1442                         term.c.attr.mode &= ~ATTR_BLINK;
1443                         break;
1444                 case 27:
1445                         term.c.attr.mode &= ~ATTR_REVERSE;
1446                         break;
1447                 case 38:
1448                         if(i + 2 < l && attr[i + 1] == 5) {
1449                                 i += 2;
1450                                 if(BETWEEN(attr[i], 0, 255)) {
1451                                         term.c.attr.fg = attr[i];
1452                                 } else {
1453                                         fprintf(stderr,
1454                                                 "erresc: bad fgcolor %d\n",
1455                                                 attr[i]);
1456                                 }
1457                         } else {
1458                                 fprintf(stderr,
1459                                         "erresc(38): gfx attr %d unknown\n",
1460                                         attr[i]);
1461                         }
1462                         break;
1463                 case 39:
1464                         term.c.attr.fg = defaultfg;
1465                         break;
1466                 case 48:
1467                         if(i + 2 < l && attr[i + 1] == 5) {
1468                                 i += 2;
1469                                 if(BETWEEN(attr[i], 0, 255)) {
1470                                         term.c.attr.bg = attr[i];
1471                                 } else {
1472                                         fprintf(stderr,
1473                                                 "erresc: bad bgcolor %d\n",
1474                                                 attr[i]);
1475                                 }
1476                         } else {
1477                                 fprintf(stderr,
1478                                         "erresc(48): gfx attr %d unknown\n",
1479                                         attr[i]);
1480                         }
1481                         break;
1482                 case 49:
1483                         term.c.attr.bg = defaultbg;
1484                         break;
1485                 default:
1486                         if(BETWEEN(attr[i], 30, 37)) {
1487                                 term.c.attr.fg = attr[i] - 30;
1488                         } else if(BETWEEN(attr[i], 40, 47)) {
1489                                 term.c.attr.bg = attr[i] - 40;
1490                         } else if(BETWEEN(attr[i], 90, 97)) {
1491                                 term.c.attr.fg = attr[i] - 90 + 8;
1492                         } else if(BETWEEN(attr[i], 100, 107)) {
1493                                 term.c.attr.bg = attr[i] - 100 + 8;
1494                         } else {
1495                                 fprintf(stderr,
1496                                         "erresc(default): gfx attr %d unknown\n",
1497                                         attr[i]), csidump();
1498                         }
1499                         break;
1500                 }
1501         }
1502 }
1503
1504 void
1505 tsetscroll(int t, int b) {
1506         int temp;
1507
1508         LIMIT(t, 0, term.row-1);
1509         LIMIT(b, 0, term.row-1);
1510         if(t > b) {
1511                 temp = t;
1512                 t = b;
1513                 b = temp;
1514         }
1515         term.top = t;
1516         term.bot = b;
1517 }
1518
1519 #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
1520
1521 void
1522 tsetmode(bool priv, bool set, int *args, int narg) {
1523         int *lim, mode;
1524         bool alt;
1525
1526         for(lim = args + narg; args < lim; ++args) {
1527                 if(priv) {
1528                         switch(*args) {
1529                                 break;
1530                         case 1: /* DECCKM -- Cursor key */
1531                                 MODBIT(term.mode, set, MODE_APPCURSOR);
1532                                 break;
1533                         case 5: /* DECSCNM -- Reverse video */
1534                                 mode = term.mode;
1535                                 MODBIT(term.mode, set, MODE_REVERSE);
1536                                 if(mode != term.mode)
1537                                         redraw(REDRAW_TIMEOUT);
1538                                 break;
1539                         case 6: /* DECOM -- Origin */
1540                                 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1541                                 tmoveato(0, 0);
1542                                 break;
1543                         case 7: /* DECAWM -- Auto wrap */
1544                                 MODBIT(term.mode, set, MODE_WRAP);
1545                                 break;
1546                         case 0:  /* Error (IGNORED) */
1547                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
1548                         case 3:  /* DECCOLM -- Column  (IGNORED) */
1549                         case 4:  /* DECSCLM -- Scroll (IGNORED) */
1550                         case 8:  /* DECARM -- Auto repeat (IGNORED) */
1551                         case 18: /* DECPFF -- Printer feed (IGNORED) */
1552                         case 19: /* DECPEX -- Printer extent (IGNORED) */
1553                         case 42: /* DECNRCM -- National characters (IGNORED) */
1554                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
1555                                 break;
1556                         case 25: /* DECTCEM -- Text Cursor Enable Mode */
1557                                 MODBIT(term.mode, !set, MODE_HIDE);
1558                                 break;
1559                         case 1000: /* 1000,1002: enable xterm mouse report */
1560                                 MODBIT(term.mode, set, MODE_MOUSEBTN);
1561                                 MODBIT(term.mode, 0, MODE_MOUSEMOTION);
1562                                 break;
1563                         case 1002:
1564                                 MODBIT(term.mode, set, MODE_MOUSEMOTION);
1565                                 MODBIT(term.mode, 0, MODE_MOUSEBTN);
1566                                 break;
1567                         case 1006:
1568                                 MODBIT(term.mode, set, MODE_MOUSESGR);
1569                                 break;
1570                         case 1049: /* = 1047 and 1048 */
1571                         case 47:
1572                         case 1047: {
1573                                 alt = IS_SET(MODE_ALTSCREEN);
1574                                 if(alt) {
1575                                         tclearregion(0, 0, term.col-1,
1576                                                         term.row-1, 0);
1577                                 }
1578                                 if(set ^ alt)           /* set is always 1 or 0 */
1579                                         tswapscreen();
1580                                 if(*args != 1049)
1581                                         break;
1582                         }
1583                                 /* pass through */
1584                         case 1048:
1585                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1586                                 break;
1587                         default:
1588                                 fprintf(stderr,
1589                                         "erresc: unknown private set/reset mode %d\n",
1590                                         *args);
1591                                 break;
1592                         }
1593                 } else {
1594                         switch(*args) {
1595                         case 0:  /* Error (IGNORED) */
1596                                 break;
1597                         case 2:  /* KAM -- keyboard action */
1598                                 MODBIT(term.mode, set, MODE_KBDLOCK);
1599                                 break;
1600                         case 4:  /* IRM -- Insertion-replacement */
1601                                 MODBIT(term.mode, set, MODE_INSERT);
1602                                 break;
1603                         case 12: /* SRM -- Send/Receive */
1604                                 MODBIT(term.mode, !set, MODE_ECHO);
1605                                 break;
1606                         case 20: /* LNM -- Linefeed/new line */
1607                                 MODBIT(term.mode, set, MODE_CRLF);
1608                                 break;
1609                         default:
1610                                 fprintf(stderr,
1611                                         "erresc: unknown set/reset mode %d\n",
1612                                         *args);
1613                                 break;
1614                         }
1615                 }
1616         }
1617 }
1618 #undef MODBIT
1619
1620
1621 void
1622 csihandle(void) {
1623         switch(csiescseq.mode) {
1624         default:
1625         unknown:
1626                 fprintf(stderr, "erresc: unknown csi ");
1627                 csidump();
1628                 /* die(""); */
1629                 break;
1630         case '@': /* ICH -- Insert <n> blank char */
1631                 DEFAULT(csiescseq.arg[0], 1);
1632                 tinsertblank(csiescseq.arg[0]);
1633                 break;
1634         case 'A': /* CUU -- Cursor <n> Up */
1635                 DEFAULT(csiescseq.arg[0], 1);
1636                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1637                 break;
1638         case 'B': /* CUD -- Cursor <n> Down */
1639         case 'e': /* VPR --Cursor <n> Down */
1640                 DEFAULT(csiescseq.arg[0], 1);
1641                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1642                 break;
1643         case 'c': /* DA -- Device Attributes */
1644                 if(csiescseq.arg[0] == 0)
1645                         ttywrite(VT102ID, sizeof(VT102ID) - 1);
1646                 break;
1647         case 'C': /* CUF -- Cursor <n> Forward */
1648         case 'a': /* HPR -- Cursor <n> Forward */
1649                 DEFAULT(csiescseq.arg[0], 1);
1650                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1651                 break;
1652         case 'D': /* CUB -- Cursor <n> Backward */
1653                 DEFAULT(csiescseq.arg[0], 1);
1654                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1655                 break;
1656         case 'E': /* CNL -- Cursor <n> Down and first col */
1657                 DEFAULT(csiescseq.arg[0], 1);
1658                 tmoveto(0, term.c.y+csiescseq.arg[0]);
1659                 break;
1660         case 'F': /* CPL -- Cursor <n> Up and first col */
1661                 DEFAULT(csiescseq.arg[0], 1);
1662                 tmoveto(0, term.c.y-csiescseq.arg[0]);
1663                 break;
1664         case 'g': /* TBC -- Tabulation clear */
1665                 switch (csiescseq.arg[0]) {
1666                 case 0: /* clear current tab stop */
1667                         term.tabs[term.c.x] = 0;
1668                         break;
1669                 case 3: /* clear all the tabs */
1670                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1671                         break;
1672                 default:
1673                         goto unknown;
1674                 }
1675                 break;
1676         case 'G': /* CHA -- Move to <col> */
1677         case '`': /* HPA */
1678                 DEFAULT(csiescseq.arg[0], 1);
1679                 tmoveto(csiescseq.arg[0]-1, term.c.y);
1680                 break;
1681         case 'H': /* CUP -- Move to <row> <col> */
1682         case 'f': /* HVP */
1683                 DEFAULT(csiescseq.arg[0], 1);
1684                 DEFAULT(csiescseq.arg[1], 1);
1685                 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1686                 break;
1687         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1688                 DEFAULT(csiescseq.arg[0], 1);
1689                 while(csiescseq.arg[0]--)
1690                         tputtab(1);
1691                 break;
1692         case 'J': /* ED -- Clear screen */
1693                 sel.bx = -1;
1694                 switch(csiescseq.arg[0]) {
1695                 case 0: /* below */
1696                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1);
1697                         if(term.c.y < term.row-1) {
1698                                 tclearregion(0, term.c.y+1, term.col-1,
1699                                                 term.row-1, 1);
1700                         }
1701                         break;
1702                 case 1: /* above */
1703                         if(term.c.y > 1)
1704                                 tclearregion(0, 0, term.col-1, term.c.y-1, 1);
1705                         tclearregion(0, term.c.y, term.c.x, term.c.y, 1);
1706                         break;
1707                 case 2: /* all */
1708                         tclearregion(0, 0, term.col-1, term.row-1, 1);
1709                         break;
1710                 default:
1711                         goto unknown;
1712                 }
1713                 break;
1714         case 'K': /* EL -- Clear line */
1715                 switch(csiescseq.arg[0]) {
1716                 case 0: /* right */
1717                         tclearregion(term.c.x, term.c.y, term.col-1,
1718                                         term.c.y, 1);
1719                         break;
1720                 case 1: /* left */
1721                         tclearregion(0, term.c.y, term.c.x, term.c.y, 1);
1722                         break;
1723                 case 2: /* all */
1724                         tclearregion(0, term.c.y, term.col-1, term.c.y, 1);
1725                         break;
1726                 }
1727                 break;
1728         case 'S': /* SU -- Scroll <n> line up */
1729                 DEFAULT(csiescseq.arg[0], 1);
1730                 tscrollup(term.top, csiescseq.arg[0]);
1731                 break;
1732         case 'T': /* SD -- Scroll <n> line down */
1733                 DEFAULT(csiescseq.arg[0], 1);
1734                 tscrolldown(term.top, csiescseq.arg[0]);
1735                 break;
1736         case 'L': /* IL -- Insert <n> blank lines */
1737                 DEFAULT(csiescseq.arg[0], 1);
1738                 tinsertblankline(csiescseq.arg[0]);
1739                 break;
1740         case 'l': /* RM -- Reset Mode */
1741                 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1742                 break;
1743         case 'M': /* DL -- Delete <n> lines */
1744                 DEFAULT(csiescseq.arg[0], 1);
1745                 tdeleteline(csiescseq.arg[0]);
1746                 break;
1747         case 'X': /* ECH -- Erase <n> char */
1748                 DEFAULT(csiescseq.arg[0], 1);
1749                 tclearregion(term.c.x, term.c.y,
1750                                 term.c.x + csiescseq.arg[0] - 1, term.c.y, 1);
1751                 break;
1752         case 'P': /* DCH -- Delete <n> char */
1753                 DEFAULT(csiescseq.arg[0], 1);
1754                 tdeletechar(csiescseq.arg[0]);
1755                 break;
1756         case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1757                 DEFAULT(csiescseq.arg[0], 1);
1758                 while(csiescseq.arg[0]--)
1759                         tputtab(0);
1760                 break;
1761         case 'd': /* VPA -- Move to <row> */
1762                 DEFAULT(csiescseq.arg[0], 1);
1763                 tmoveato(term.c.x, csiescseq.arg[0]-1);
1764                 break;
1765         case 'h': /* SM -- Set terminal mode */
1766                 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1767                 break;
1768         case 'm': /* SGR -- Terminal attribute (color) */
1769                 tsetattr(csiescseq.arg, csiescseq.narg);
1770                 break;
1771         case 'r': /* DECSTBM -- Set Scrolling Region */
1772                 if(csiescseq.priv) {
1773                         goto unknown;
1774                 } else {
1775                         DEFAULT(csiescseq.arg[0], 1);
1776                         DEFAULT(csiescseq.arg[1], term.row);
1777                         tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1778                         tmoveato(0, 0);
1779                 }
1780                 break;
1781         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1782                 tcursor(CURSOR_SAVE);
1783                 break;
1784         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1785                 tcursor(CURSOR_LOAD);
1786                 break;
1787         }
1788 }
1789
1790 void
1791 csidump(void) {
1792         int i;
1793         uint c;
1794
1795         printf("ESC[");
1796         for(i = 0; i < csiescseq.len; i++) {
1797                 c = csiescseq.buf[i] & 0xff;
1798                 if(isprint(c)) {
1799                         putchar(c);
1800                 } else if(c == '\n') {
1801                         printf("(\\n)");
1802                 } else if(c == '\r') {
1803                         printf("(\\r)");
1804                 } else if(c == 0x1b) {
1805                         printf("(\\e)");
1806                 } else {
1807                         printf("(%02x)", c);
1808                 }
1809         }
1810         putchar('\n');
1811 }
1812
1813 void
1814 csireset(void) {
1815         memset(&csiescseq, 0, sizeof(csiescseq));
1816 }
1817
1818 void
1819 strhandle(void) {
1820         char *p;
1821
1822         /*
1823          * TODO: make this being useful in case of color palette change.
1824          */
1825         strparse();
1826
1827         p = strescseq.buf;
1828
1829         switch(strescseq.type) {
1830         case ']': /* OSC -- Operating System Command */
1831                 switch(p[0]) {
1832                 case '0':
1833                 case '1':
1834                 case '2':
1835                         /*
1836                          * TODO: Handle special chars in string, like umlauts.
1837                          */
1838                         if(p[1] == ';') {
1839                                 XStoreName(xw.dpy, xw.win, strescseq.buf+2);
1840                         }
1841                         break;
1842                 case ';':
1843                         XStoreName(xw.dpy, xw.win, strescseq.buf+1);
1844                         break;
1845                 case '4': /* TODO: Set color (arg0) to "rgb:%hexr/$hexg/$hexb" (arg1) */
1846                         break;
1847                 default:
1848                         fprintf(stderr, "erresc: unknown str ");
1849                         strdump();
1850                         break;
1851                 }
1852                 break;
1853         case 'k': /* old title set compatibility */
1854                 XStoreName(xw.dpy, xw.win, strescseq.buf);
1855                 break;
1856         case 'P': /* DSC -- Device Control String */
1857         case '_': /* APC -- Application Program Command */
1858         case '^': /* PM -- Privacy Message */
1859         default:
1860                 fprintf(stderr, "erresc: unknown str ");
1861                 strdump();
1862                 /* die(""); */
1863                 break;
1864         }
1865 }
1866
1867 void
1868 strparse(void) {
1869         /*
1870          * TODO: Implement parsing like for CSI when required.
1871          * Format: ESC type cmd ';' arg0 [';' argn] ESC \
1872          */
1873         return;
1874 }
1875
1876 void
1877 strdump(void) {
1878         int i;
1879         uint c;
1880
1881         printf("ESC%c", strescseq.type);
1882         for(i = 0; i < strescseq.len; i++) {
1883                 c = strescseq.buf[i] & 0xff;
1884                 if(isprint(c)) {
1885                         putchar(c);
1886                 } else if(c == '\n') {
1887                         printf("(\\n)");
1888                 } else if(c == '\r') {
1889                         printf("(\\r)");
1890                 } else if(c == 0x1b) {
1891                         printf("(\\e)");
1892                 } else {
1893                         printf("(%02x)", c);
1894                 }
1895         }
1896         printf("ESC\\\n");
1897 }
1898
1899 void
1900 strreset(void) {
1901         memset(&strescseq, 0, sizeof(strescseq));
1902 }
1903
1904 void
1905 tputtab(bool forward) {
1906         uint x = term.c.x;
1907
1908         if(forward) {
1909                 if(x == term.col)
1910                         return;
1911                 for(++x; x < term.col && !term.tabs[x]; ++x)
1912                         /* nothing */ ;
1913         } else {
1914                 if(x == 0)
1915                         return;
1916                 for(--x; x > 0 && !term.tabs[x]; --x)
1917                         /* nothing */ ;
1918         }
1919         tmoveto(x, term.c.y);
1920 }
1921
1922 void
1923 techo(char *buf, int len) {
1924         for(; len > 0; buf++, len--) {
1925                 char c = *buf;
1926
1927                 if(c == '\033') {               /* escape */
1928                         tputc("^", 1);
1929                         tputc("[", 1);
1930                 } else if (c < '\x20') {        /* control code */
1931                         if(c != '\n' && c != '\r' && c != '\t') {
1932                                 c |= '\x40';
1933                                 tputc("^", 1);
1934                         }
1935                         tputc(&c, 1);
1936                 } else {
1937                         break;
1938                 }
1939         }
1940         if (len)
1941                 tputc(buf, len);
1942 }
1943
1944 void
1945 tputc(char *c, int len) {
1946         uchar ascii = *c;
1947         bool control = ascii < '\x20' || ascii == 0177;
1948
1949         if(iofd != -1) {
1950                 if (xwrite(iofd, c, len) < 0) {
1951                         fprintf(stderr, "Error writing in %s:%s\n",
1952                                 opt_io, strerror(errno));
1953                         close(iofd);
1954                         iofd = -1;
1955                 }
1956         }
1957
1958         /*
1959          * STR sequences must be checked before anything else
1960          * because it can use some control codes as part of the sequence.
1961          */
1962         if(term.esc & ESC_STR) {
1963                 switch(ascii) {
1964                 case '\033':
1965                         term.esc = ESC_START | ESC_STR_END;
1966                         break;
1967                 case '\a': /* backwards compatibility to xterm */
1968                         term.esc = 0;
1969                         strhandle();
1970                         break;
1971                 default:
1972                         if(strescseq.len + len < sizeof(strescseq.buf)) {
1973                                 memmove(&strescseq.buf[strescseq.len], c, len);
1974                                 strescseq.len += len;
1975                         } else {
1976                         /*
1977                          * Here is a bug in terminals. If the user never sends
1978                          * some code to stop the str or esc command, then st
1979                          * will stop responding. But this is better than
1980                          * silently failing with unknown characters. At least
1981                          * then users will report back.
1982                          *
1983                          * In the case users ever get fixed, here is the code:
1984                          */
1985                         /*
1986                          * term.esc = 0;
1987                          * strhandle();
1988                          */
1989                         }
1990                 }
1991                 return;
1992         }
1993
1994         /*
1995          * Actions of control codes must be performed as soon they arrive
1996          * because they can be embedded inside a control sequence, and
1997          * they must not cause conflicts with sequences.
1998          */
1999         if(control) {
2000                 switch(ascii) {
2001                 case '\t':      /* HT */
2002                         tputtab(1);
2003                         return;
2004                 case '\b':      /* BS */
2005                         tmoveto(term.c.x-1, term.c.y);
2006                         return;
2007                 case '\r':      /* CR */
2008                         tmoveto(0, term.c.y);
2009                         return;
2010                 case '\f':      /* LF */
2011                 case '\v':      /* VT */
2012                 case '\n':      /* LF */
2013                         /* go to first col if the mode is set */
2014                         tnewline(IS_SET(MODE_CRLF));
2015                         return;
2016                 case '\a':      /* BEL */
2017                         if(!(xw.state & WIN_FOCUSED))
2018                                 xseturgency(1);
2019                         return;
2020                 case '\033':    /* ESC */
2021                         csireset();
2022                         term.esc = ESC_START;
2023                         return;
2024                 case '\016':    /* SO */
2025                 case '\017':    /* SI */
2026                         /*
2027                          * Different charsets are hard to handle. Applications
2028                          * should use the right alt charset escapes for the
2029                          * only reason they still exist: line drawing. The
2030                          * rest is incompatible history st should not support.
2031                          */
2032                         return;
2033                 case '\032':    /* SUB */
2034                 case '\030':    /* CAN */
2035                         csireset();
2036                         return;
2037                 case '\005':    /* ENQ (IGNORED) */
2038                 case '\000':    /* NUL (IGNORED) */
2039                 case '\021':    /* XON (IGNORED) */
2040                 case '\023':    /* XOFF (IGNORED) */
2041                 case 0177:      /* DEL (IGNORED) */
2042                         return;
2043                 }
2044         } else if(term.esc & ESC_START) {
2045                 if(term.esc & ESC_CSI) {
2046                         csiescseq.buf[csiescseq.len++] = ascii;
2047                         if(BETWEEN(ascii, 0x40, 0x7E)
2048                                         || csiescseq.len >= ESC_BUF_SIZ) {
2049                                 term.esc = 0;
2050                                 csiparse(), csihandle();
2051                         }
2052                 } else if(term.esc & ESC_STR_END) {
2053                         term.esc = 0;
2054                         if(ascii == '\\')
2055                                 strhandle();
2056                 } else if(term.esc & ESC_ALTCHARSET) {
2057                         switch(ascii) {
2058                         case '0': /* Line drawing set */
2059                                 term.c.attr.mode |= ATTR_GFX;
2060                                 break;
2061                         case 'B': /* USASCII */
2062                                 term.c.attr.mode &= ~ATTR_GFX;
2063                                 break;
2064                         case 'A': /* UK (IGNORED) */
2065                         case '<': /* multinational charset (IGNORED) */
2066                         case '5': /* Finnish (IGNORED) */
2067                         case 'C': /* Finnish (IGNORED) */
2068                         case 'K': /* German (IGNORED) */
2069                                 break;
2070                         default:
2071                                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2072                         }
2073                         term.esc = 0;
2074                 } else if(term.esc & ESC_TEST) {
2075                         if(ascii == '8') { /* DEC screen alignment test. */
2076                                 char E[UTF_SIZ] = "E";
2077                                 int x, y;
2078
2079                                 for(x = 0; x < term.col; ++x) {
2080                                         for(y = 0; y < term.row; ++y)
2081                                                 tsetchar(E, &term.c.attr, x, y);
2082                                 }
2083                         }
2084                         term.esc = 0;
2085                 } else {
2086                         switch(ascii) {
2087                         case '[':
2088                                 term.esc |= ESC_CSI;
2089                                 break;
2090                         case '#':
2091                                 term.esc |= ESC_TEST;
2092                                 break;
2093                         case 'P': /* DCS -- Device Control String */
2094                         case '_': /* APC -- Application Program Command */
2095                         case '^': /* PM -- Privacy Message */
2096                         case ']': /* OSC -- Operating System Command */
2097                         case 'k': /* old title set compatibility */
2098                                 strreset();
2099                                 strescseq.type = ascii;
2100                                 term.esc |= ESC_STR;
2101                                 break;
2102                         case '(': /* set primary charset G0 */
2103                                 term.esc |= ESC_ALTCHARSET;
2104                                 break;
2105                         case ')': /* set secondary charset G1 (IGNORED) */
2106                         case '*': /* set tertiary charset G2 (IGNORED) */
2107                         case '+': /* set quaternary charset G3 (IGNORED) */
2108                                 term.esc = 0;
2109                                 break;
2110                         case 'D': /* IND -- Linefeed */
2111                                 if(term.c.y == term.bot) {
2112                                         tscrollup(term.top, 1);
2113                                 } else {
2114                                         tmoveto(term.c.x, term.c.y+1);
2115                                 }
2116                                 term.esc = 0;
2117                                 break;
2118                         case 'E': /* NEL -- Next line */
2119                                 tnewline(1); /* always go to first col */
2120                                 term.esc = 0;
2121                                 break;
2122                         case 'H': /* HTS -- Horizontal tab stop */
2123                                 term.tabs[term.c.x] = 1;
2124                                 term.esc = 0;
2125                                 break;
2126                         case 'M': /* RI -- Reverse index */
2127                                 if(term.c.y == term.top) {
2128                                         tscrolldown(term.top, 1);
2129                                 } else {
2130                                         tmoveto(term.c.x, term.c.y-1);
2131                                 }
2132                                 term.esc = 0;
2133                                 break;
2134                         case 'Z': /* DECID -- Identify Terminal */
2135                                 ttywrite(VT102ID, sizeof(VT102ID) - 1);
2136                                 term.esc = 0;
2137                                 break;
2138                         case 'c': /* RIS -- Reset to inital state */
2139                                 treset();
2140                                 term.esc = 0;
2141                                 xresettitle();
2142                                 break;
2143                         case '=': /* DECPAM -- Application keypad */
2144                                 term.mode |= MODE_APPKEYPAD;
2145                                 term.esc = 0;
2146                                 break;
2147                         case '>': /* DECPNM -- Normal keypad */
2148                                 term.mode &= ~MODE_APPKEYPAD;
2149                                 term.esc = 0;
2150                                 break;
2151                         case '7': /* DECSC -- Save Cursor */
2152                                 tcursor(CURSOR_SAVE);
2153                                 term.esc = 0;
2154                                 break;
2155                         case '8': /* DECRC -- Restore Cursor */
2156                                 tcursor(CURSOR_LOAD);
2157                                 term.esc = 0;
2158                                 break;
2159                         case '\\': /* ST -- Stop */
2160                                 term.esc = 0;
2161                                 break;
2162                         default:
2163                                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2164                                         (uchar) ascii, isprint(ascii)? ascii:'.');
2165                                 term.esc = 0;
2166                         }
2167                 }
2168                 /*
2169                  * All characters which form part of a sequence are not
2170                  * printed
2171                  */
2172                 return;
2173         }
2174         /*
2175          * Display control codes only if we are in graphic mode
2176          */
2177         if(control && !(term.c.attr.mode & ATTR_GFX))
2178                 return;
2179         if(sel.bx != -1 && BETWEEN(term.c.y, sel.by, sel.ey))
2180                 sel.bx = -1;
2181         if(IS_SET(MODE_WRAP) && term.c.state & CURSOR_WRAPNEXT)
2182                 tnewline(1); /* always go to first col */
2183
2184         if(IS_SET(MODE_INSERT) && term.c.x+1 < term.col) {
2185                 memmove(&term.line[term.c.y][term.c.x+1],
2186                         &term.line[term.c.y][term.c.x],
2187                         (term.col - term.c.x - 1) * sizeof(Glyph));
2188         }
2189
2190         tsetchar(c, &term.c.attr, term.c.x, term.c.y);
2191         if(term.c.x+1 < term.col) {
2192                 tmoveto(term.c.x+1, term.c.y);
2193         } else {
2194                 term.c.state |= CURSOR_WRAPNEXT;
2195         }
2196 }
2197
2198 int
2199 tresize(int col, int row) {
2200         int i, x;
2201         int minrow = MIN(row, term.row);
2202         int mincol = MIN(col, term.col);
2203         int slide = term.c.y - row + 1;
2204         bool *bp;
2205
2206         if(col < 1 || row < 1)
2207                 return 0;
2208
2209         /* free unneeded rows */
2210         i = 0;
2211         if(slide > 0) {
2212                 /*
2213                  * slide screen to keep cursor where we expect it -
2214                  * tscrollup would work here, but we can optimize to
2215                  * memmove because we're freeing the earlier lines
2216                  */
2217                 for(/* i = 0 */; i < slide; i++) {
2218                         free(term.line[i]);
2219                         free(term.alt[i]);
2220                 }
2221                 memmove(term.line, term.line + slide, row * sizeof(Line));
2222                 memmove(term.alt, term.alt + slide, row * sizeof(Line));
2223         }
2224         for(i += row; i < term.row; i++) {
2225                 free(term.line[i]);
2226                 free(term.alt[i]);
2227         }
2228
2229         /* resize to new height */
2230         term.line = xrealloc(term.line, row * sizeof(Line));
2231         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
2232         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2233         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2234
2235         /* resize each row to new width, zero-pad if needed */
2236         for(i = 0; i < minrow; i++) {
2237                 term.dirty[i] = 1;
2238                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2239                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
2240                 for(x = mincol; x < col; x++) {
2241                         term.line[i][x].state = 0;
2242                         term.alt[i][x].state = 0;
2243                 }
2244         }
2245
2246         /* allocate any new rows */
2247         for(/* i == minrow */; i < row; i++) {
2248                 term.dirty[i] = 1;
2249                 term.line[i] = xcalloc(col, sizeof(Glyph));
2250                 term.alt [i] = xcalloc(col, sizeof(Glyph));
2251         }
2252         if(col > term.col) {
2253                 bp = term.tabs + term.col;
2254
2255                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2256                 while(--bp > term.tabs && !*bp)
2257                         /* nothing */ ;
2258                 for(bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2259                         *bp = 1;
2260         }
2261         /* update terminal size */
2262         term.col = col;
2263         term.row = row;
2264         /* reset scrolling region */
2265         tsetscroll(0, row-1);
2266         /* make use of the LIMIT in tmoveto */
2267         tmoveto(term.c.x, term.c.y);
2268
2269         return (slide > 0);
2270 }
2271
2272 void
2273 xresize(int col, int row) {
2274         xw.tw = MAX(1, col * xw.cw);
2275         xw.th = MAX(1, row * xw.ch);
2276
2277         if(!usedbe) {
2278                 XFreePixmap(xw.dpy, xw.buf);
2279                 xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h,
2280                                 DefaultDepth(xw.dpy, xw.scr));
2281                 XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg].pixel);
2282                 XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, xw.w, xw.h);
2283         }
2284
2285         XftDrawChange(xw.draw, xw.buf);
2286 }
2287
2288 void
2289 xloadcols(void) {
2290         int i, r, g, b;
2291         XRenderColor color = { .alpha = 0 };
2292
2293         /* load colors [0-15] colors and [256-LEN(colorname)[ (config.h) */
2294         for(i = 0; i < LEN(colorname); i++) {
2295                 if(!colorname[i])
2296                         continue;
2297                 if(!XftColorAllocName(xw.dpy, xw.vis, xw.cmap, colorname[i], &dc.col[i])) {
2298                         die("Could not allocate color '%s'\n", colorname[i]);
2299                 }
2300         }
2301
2302         /* load colors [16-255] ; same colors as xterm */
2303         for(i = 16, r = 0; r < 6; r++) {
2304                 for(g = 0; g < 6; g++) {
2305                         for(b = 0; b < 6; b++) {
2306                                 color.red = r == 0 ? 0 : 0x3737 + 0x2828 * r;
2307                                 color.green = g == 0 ? 0 : 0x3737 + 0x2828 * g;
2308                                 color.blue = b == 0 ? 0 : 0x3737 + 0x2828 * b;
2309                                 if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, &dc.col[i])) {
2310                                         die("Could not allocate color %d\n", i);
2311                                 }
2312                                 i++;
2313                         }
2314                 }
2315         }
2316
2317         for(r = 0; r < 24; r++, i++) {
2318                 color.red = color.green = color.blue = 0x0808 + 0x0a0a * r;
2319                 if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color,
2320                                         &dc.col[i])) {
2321                         die("Could not allocate color %d\n", i);
2322                 }
2323         }
2324 }
2325
2326 void
2327 xtermclear(int col1, int row1, int col2, int row2) {
2328         XftDrawRect(xw.draw,
2329                         &dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg],
2330                         borderpx + col1 * xw.cw,
2331                         borderpx + row1 * xw.ch,
2332                         (col2-col1+1) * xw.cw,
2333                         (row2-row1+1) * xw.ch);
2334 }
2335
2336 /*
2337  * Absolute coordinates.
2338  */
2339 void
2340 xclear(int x1, int y1, int x2, int y2) {
2341         XftDrawRect(xw.draw,
2342                         &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg],
2343                         x1, y1, x2-x1, y2-y1);
2344 }
2345
2346 void
2347 xhints(void) {
2348         XClassHint class = {opt_class ? opt_class : termname, termname};
2349         XWMHints wm = {.flags = InputHint, .input = 1};
2350         XSizeHints *sizeh = NULL;
2351
2352         sizeh = XAllocSizeHints();
2353         if(xw.isfixed == False) {
2354                 sizeh->flags = PSize | PResizeInc | PBaseSize;
2355                 sizeh->height = xw.h;
2356                 sizeh->width = xw.w;
2357                 sizeh->height_inc = xw.ch;
2358                 sizeh->width_inc = xw.cw;
2359                 sizeh->base_height = 2 * borderpx;
2360                 sizeh->base_width = 2 * borderpx;
2361         } else {
2362                 sizeh->flags = PMaxSize | PMinSize;
2363                 sizeh->min_width = sizeh->max_width = xw.fw;
2364                 sizeh->min_height = sizeh->max_height = xw.fh;
2365         }
2366
2367         XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, &class);
2368         XFree(sizeh);
2369 }
2370
2371 int
2372 xloadfont(Font *f, FcPattern *pattern) {
2373         FcPattern *match;
2374         FcResult result;
2375
2376         match = FcFontMatch(NULL, pattern, &result);
2377         if(!match)
2378                 return 1;
2379
2380         if(!(f->set = FcFontSort(0, match, FcTrue, 0, &result))) {
2381                 FcPatternDestroy(match);
2382                 return 1;
2383         }
2384
2385         if(!(f->match = XftFontOpenPattern(xw.dpy, match))) {
2386                 FcPatternDestroy(match);
2387                 return 1;
2388         }
2389
2390         f->pattern = FcPatternDuplicate(pattern);
2391
2392         f->ascent = f->match->ascent;
2393         f->descent = f->match->descent;
2394         f->lbearing = 0;
2395         f->rbearing = f->match->max_advance_width;
2396
2397         f->height = f->match->height;
2398         f->width = f->lbearing + f->rbearing;
2399
2400         return 0;
2401 }
2402
2403 void
2404 xloadfonts(char *fontstr, int fontsize) {
2405         FcPattern *pattern;
2406         FcResult result;
2407         double fontval;
2408
2409         if(fontstr[0] == '-') {
2410                 pattern = XftXlfdParse(fontstr, False, False);
2411         } else {
2412                 pattern = FcNameParse((FcChar8 *)fontstr);
2413         }
2414
2415         if(!pattern)
2416                 die("st: can't open font %s\n", fontstr);
2417
2418         if(fontsize > 0) {
2419                 FcPatternDel(pattern, FC_PIXEL_SIZE);
2420                 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize);
2421                 usedfontsize = fontsize;
2422         } else {
2423                 result = FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval);
2424                 if(result == FcResultMatch) {
2425                         usedfontsize = (int)fontval;
2426                 } else {
2427                         /*
2428                          * Default font size is 12, if none given. This is to
2429                          * have a known usedfontsize value.
2430                          */
2431                         FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);
2432                         usedfontsize = 12;
2433                 }
2434         }
2435
2436         FcConfigSubstitute(0, pattern, FcMatchPattern);
2437         FcDefaultSubstitute(pattern);
2438
2439         if(xloadfont(&dc.font, pattern))
2440                 die("st: can't open font %s\n", fontstr);
2441
2442         /* Setting character width and height. */
2443         xw.cw = dc.font.width;
2444         xw.ch = dc.font.height;
2445
2446         FcPatternDel(pattern, FC_SLANT);
2447         FcPatternDel(pattern, FC_WEIGHT);
2448         FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
2449         FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
2450         if(xloadfont(&dc.bfont, pattern))
2451                 die("st: can't open font %s\n", fontstr);
2452
2453         FcPatternDel(pattern, FC_SLANT);
2454         FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
2455         if(xloadfont(&dc.ibfont, pattern))
2456                 die("st: can't open font %s\n", fontstr);
2457
2458         FcPatternDel(pattern, FC_WEIGHT);
2459         FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_MEDIUM);
2460         if(xloadfont(&dc.ifont, pattern))
2461                 die("st: can't open font %s\n", fontstr);
2462
2463         FcPatternDestroy(pattern);
2464 }
2465
2466 void
2467 xunloadfonts(void) {
2468         int i, ip;
2469
2470         /*
2471          * Free the loaded fonts in the font cache. This is done backwards
2472          * from the frccur.
2473          */
2474         for (i = 0, ip = frccur; i < frclen; i++, ip--) {
2475                 if (ip < 0)
2476                         ip = LEN(frc) - 1;
2477                 XftFontClose(xw.dpy, frc[ip].font);
2478         }
2479         frccur = -1;
2480         frclen = 0;
2481
2482         XftFontClose(xw.dpy, dc.font.match);
2483         FcPatternDestroy(dc.font.pattern);
2484         FcFontSetDestroy(dc.font.set);
2485         XftFontClose(xw.dpy, dc.bfont.match);
2486         FcPatternDestroy(dc.bfont.pattern);
2487         FcFontSetDestroy(dc.bfont.set);
2488         XftFontClose(xw.dpy, dc.ifont.match);
2489         FcPatternDestroy(dc.ifont.pattern);
2490         FcFontSetDestroy(dc.ifont.set);
2491         XftFontClose(xw.dpy, dc.ibfont.match);
2492         FcPatternDestroy(dc.ibfont.pattern);
2493         FcFontSetDestroy(dc.ibfont.set);
2494 }
2495
2496 void
2497 xzoom(const Arg *arg) {
2498         xunloadfonts();
2499         xloadfonts(usedfont, usedfontsize + arg->i);
2500         cresize(0, 0);
2501         redraw(0);
2502 }
2503
2504 void
2505 xinit(void) {
2506         XSetWindowAttributes attrs;
2507         XGCValues gcvalues;
2508         Cursor cursor;
2509         Window parent;
2510         int sw, sh, major, minor;
2511
2512         if(!(xw.dpy = XOpenDisplay(NULL)))
2513                 die("Can't open display\n");
2514         xw.scr = XDefaultScreen(xw.dpy);
2515         xw.vis = XDefaultVisual(xw.dpy, xw.scr);
2516
2517         /* font */
2518         if (!FcInit())
2519                 die("Could not init fontconfig.\n");
2520
2521         usedfont = (opt_font == NULL)? font : opt_font;
2522         xloadfonts(usedfont, 0);
2523
2524         /* colors */
2525         xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
2526         xloadcols();
2527
2528         /* adjust fixed window geometry */
2529         if(xw.isfixed) {
2530                 sw = DisplayWidth(xw.dpy, xw.scr);
2531                 sh = DisplayHeight(xw.dpy, xw.scr);
2532                 if(xw.fx < 0)
2533                         xw.fx = sw + xw.fx - xw.fw - 1;
2534                 if(xw.fy < 0)
2535                         xw.fy = sh + xw.fy - xw.fh - 1;
2536
2537                 xw.h = xw.fh;
2538                 xw.w = xw.fw;
2539         } else {
2540                 /* window - default size */
2541                 xw.h = 2 * borderpx + term.row * xw.ch;
2542                 xw.w = 2 * borderpx + term.col * xw.cw;
2543                 xw.fx = 0;
2544                 xw.fy = 0;
2545         }
2546
2547         /* Events */
2548         attrs.background_pixel = dc.col[defaultbg].pixel;
2549         attrs.border_pixel = dc.col[defaultbg].pixel;
2550         attrs.bit_gravity = NorthWestGravity;
2551         attrs.event_mask = FocusChangeMask | KeyPressMask
2552                 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
2553                 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
2554         attrs.colormap = xw.cmap;
2555
2556         parent = opt_embed ? strtol(opt_embed, NULL, 0) : \
2557                         XRootWindow(xw.dpy, xw.scr);
2558         xw.win = XCreateWindow(xw.dpy, parent, xw.fx, xw.fy,
2559                         xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
2560                         xw.vis,
2561                         CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask
2562                         | CWColormap,
2563                         &attrs);
2564
2565         /* double buffering */
2566         /*
2567         if(XdbeQueryExtension(xw.dpy, &major, &minor)) {
2568                 xw.buf = XdbeAllocateBackBufferName(xw.dpy, xw.win,
2569                                 XdbeBackground);
2570                 usedbe = True;
2571         } else {
2572         */
2573                 memset(&gcvalues, 0, sizeof(gcvalues));
2574                 gcvalues.graphics_exposures = False;
2575                 dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures,
2576                                 &gcvalues);
2577                 xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h,
2578                                 DefaultDepth(xw.dpy, xw.scr));
2579                 XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
2580                 XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, xw.w, xw.h);
2581                 //xw.buf = xw.win;
2582         /*
2583         }
2584         */
2585
2586         /* Xft rendering context */
2587         xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
2588
2589         /* input methods */
2590         if((xw.xim =  XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) {
2591                 XSetLocaleModifiers("@im=local");
2592                 if((xw.xim =  XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) {
2593                         XSetLocaleModifiers("@im=");
2594                         if((xw.xim = XOpenIM(xw.dpy,
2595                                         NULL, NULL, NULL)) == NULL) {
2596                                 die("XOpenIM failed. Could not open input"
2597                                         " device.\n");
2598                         }
2599                 }
2600         }
2601         xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing
2602                                            | XIMStatusNothing, XNClientWindow, xw.win,
2603                                            XNFocusWindow, xw.win, NULL);
2604         if(xw.xic == NULL)
2605                 die("XCreateIC failed. Could not obtain input method.\n");
2606
2607         /* white cursor, black outline */
2608         cursor = XCreateFontCursor(xw.dpy, XC_xterm);
2609         XDefineCursor(xw.dpy, xw.win, cursor);
2610         XRecolorCursor(xw.dpy, cursor,
2611                 &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff},
2612                 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
2613
2614         xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
2615         xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
2616         XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
2617
2618         xresettitle();
2619         XMapWindow(xw.dpy, xw.win);
2620         xhints();
2621         XSync(xw.dpy, 0);
2622 }
2623
2624 void
2625 xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
2626         int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch,
2627             width = charlen * xw.cw, xp, i;
2628         int frp, frcflags;
2629         int u8fl, u8fblen, u8cblen, doesexist;
2630         char *u8c, *u8fs;
2631         long u8char;
2632         Font *font = &dc.font;
2633         FcResult fcres;
2634         FcPattern *fcpattern, *fontpattern;
2635         FcFontSet *fcsets[] = { NULL };
2636         FcCharSet *fccharset;
2637         Colour *fg = &dc.col[base.fg], *bg = &dc.col[base.bg],
2638                  *temp, revfg, revbg;
2639         XRenderColor colfg, colbg;
2640
2641         frcflags = FRC_NORMAL;
2642
2643         if(base.mode & ATTR_BOLD) {
2644                 if(BETWEEN(base.fg, 0, 7)) {
2645                         /* basic system colors */
2646                         fg = &dc.col[base.fg + 8];
2647                 } else if(BETWEEN(base.fg, 16, 195)) {
2648                         /* 256 colors */
2649                         fg = &dc.col[base.fg + 36];
2650                 } else if(BETWEEN(base.fg, 232, 251)) {
2651                         /* greyscale */
2652                         fg = &dc.col[base.fg + 4];
2653                 }
2654                 /*
2655                  * Those ranges will not be brightened:
2656                  *      8 - 15 – bright system colors
2657                  *      196 - 231 – highest 256 color cube
2658                  *      252 - 255 – brightest colors in greyscale
2659                  */
2660                 font = &dc.bfont;
2661                 frcflags = FRC_BOLD;
2662         }
2663
2664         if(base.mode & ATTR_ITALIC) {
2665                 font = &dc.ifont;
2666                 frcflags = FRC_ITALIC;
2667         }
2668         if((base.mode & ATTR_ITALIC) && (base.mode & ATTR_BOLD)) {
2669                 font = &dc.ibfont;
2670                 frcflags = FRC_ITALICBOLD;
2671         }
2672
2673         if(IS_SET(MODE_REVERSE)) {
2674                 if(fg == &dc.col[defaultfg]) {
2675                         fg = &dc.col[defaultbg];
2676                 } else {
2677                         colfg.red = ~fg->color.red;
2678                         colfg.green = ~fg->color.green;
2679                         colfg.blue = ~fg->color.blue;
2680                         colfg.alpha = fg->color.alpha;
2681                         XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg);
2682                         fg = &revfg;
2683                 }
2684
2685                 if(bg == &dc.col[defaultbg]) {
2686                         bg = &dc.col[defaultfg];
2687                 } else {
2688                         colbg.red = ~bg->color.red;
2689                         colbg.green = ~bg->color.green;
2690                         colbg.blue = ~bg->color.blue;
2691                         colbg.alpha = bg->color.alpha;
2692                         XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &revbg);
2693                         bg = &revbg;
2694                 }
2695         }
2696
2697         if(base.mode & ATTR_REVERSE) {
2698                 temp = fg;
2699                 fg = bg;
2700                 bg = temp;
2701         }
2702
2703         /* Intelligent cleaning up of the borders. */
2704         if(x == 0) {
2705                 xclear(0, (y == 0)? 0 : winy, borderpx,
2706                         winy + xw.ch + ((y >= term.row-1)? xw.h : 0));
2707         }
2708         if(x + charlen >= term.col) {
2709                 xclear(winx + width, (y == 0)? 0 : winy, xw.w,
2710                         ((y >= term.row-1)? xw.h : (winy + xw.ch)));
2711         }
2712         if(y == 0)
2713                 xclear(winx, 0, winx + width, borderpx);
2714         if(y == term.row-1)
2715                 xclear(winx, winy + xw.ch, winx + width, xw.h);
2716
2717         /* Clean up the region we want to draw to. */
2718         XftDrawRect(xw.draw, bg, winx, winy, width, xw.ch);
2719
2720         fcsets[0] = font->set;
2721         for (xp = winx; bytelen > 0;) {
2722                 /*
2723                  * Search for the range in the to be printed string of glyphs
2724                  * that are in the main font. Then print that range. If
2725                  * some glyph is found that is not in the font, do the
2726                  * fallback dance.
2727                  */
2728                 u8fs = s;
2729                 u8fblen = 0;
2730                 u8fl = 0;
2731                 for (;;) {
2732                         u8c = s;
2733                         u8cblen = utf8decode(s, &u8char);
2734                         s += u8cblen;
2735                         bytelen -= u8cblen;
2736
2737                         doesexist = XftCharIndex(xw.dpy, font->match, u8char);
2738                         if (!doesexist || bytelen <= 0) {
2739                                 if (bytelen <= 0) {
2740                                         if (doesexist) {
2741                                                 u8fl++;
2742                                                 u8fblen += u8cblen;
2743                                         }
2744                                 }
2745
2746                                 if (u8fl > 0) {
2747                                         XftDrawStringUtf8(xw.draw, fg,
2748                                                         font->match, xp,
2749                                                         winy + font->ascent,
2750                                                         (FcChar8 *)u8fs,
2751                                                         u8fblen);
2752                                         xp += font->width * u8fl;
2753                                 }
2754                                 break;
2755                         }
2756
2757                         u8fl++;
2758                         u8fblen += u8cblen;
2759                 }
2760                 if (doesexist)
2761                         break;
2762
2763                 frp = frccur;
2764                 /* Search the font cache. */
2765                 for (i = 0; i < frclen; i++, frp--) {
2766                         if (frp <= 0)
2767                                 frp = LEN(frc) - 1;
2768
2769                         if (frc[frp].c == u8char
2770                                         && frc[frp].flags == frcflags) {
2771                                 break;
2772                         }
2773                 }
2774
2775                 /* Nothing was found. */
2776                 if (i >= frclen) {
2777                         /*
2778                          * Nothing was found in the cache. Now use
2779                          * some dozen of Fontconfig calls to get the
2780                          * font for one single character.
2781                          */
2782                         fcpattern = FcPatternDuplicate(font->pattern);
2783                         fccharset = FcCharSetCreate();
2784
2785                         FcCharSetAddChar(fccharset, u8char);
2786                         FcPatternAddCharSet(fcpattern, FC_CHARSET,
2787                                         fccharset);
2788                         FcPatternAddBool(fcpattern, FC_SCALABLE,
2789                                         FcTrue);
2790
2791                         FcConfigSubstitute(0, fcpattern,
2792                                         FcMatchPattern);
2793                         FcDefaultSubstitute(fcpattern);
2794
2795                         fontpattern = FcFontSetMatch(0, fcsets,
2796                                         FcTrue, fcpattern, &fcres);
2797
2798                         /*
2799                          * Overwrite or create the new cache entry
2800                          * entry.
2801                          */
2802                         frccur++;
2803                         frclen++;
2804                         if (frccur >= LEN(frc))
2805                                 frccur = 0;
2806                         if (frclen > LEN(frc)) {
2807                                 frclen = LEN(frc);
2808                                 XftFontClose(xw.dpy, frc[frccur].font);
2809                         }
2810
2811                         frc[frccur].font = XftFontOpenPattern(xw.dpy,
2812                                         fontpattern);
2813                         frc[frccur].c = u8char;
2814                         frc[frccur].flags = frcflags;
2815
2816                         FcPatternDestroy(fcpattern);
2817                         FcCharSetDestroy(fccharset);
2818
2819                         frp = frccur;
2820                 }
2821
2822                 XftDrawStringUtf8(xw.draw, fg, frc[frp].font,
2823                                 xp, winy + frc[frp].font->ascent,
2824                                 (FcChar8 *)u8c, u8cblen);
2825
2826                 xp += font->width;
2827         }
2828
2829         /*
2830         XftDrawStringUtf8(xw.draw, fg, font->set, winx,
2831                         winy + font->ascent, (FcChar8 *)s, bytelen);
2832         */
2833
2834         if(base.mode & ATTR_UNDERLINE) {
2835                 XftDrawRect(xw.draw, fg, winx, winy + font->ascent + 1,
2836                                 width, 1);
2837         }
2838 }
2839
2840 void
2841 xdrawcursor(void) {
2842         static int oldx = 0, oldy = 0;
2843         int sl;
2844         Glyph g = {{' '}, ATTR_NULL, defaultbg, defaultcs, 0};
2845
2846         LIMIT(oldx, 0, term.col-1);
2847         LIMIT(oldy, 0, term.row-1);
2848
2849         if(term.line[term.c.y][term.c.x].state & GLYPH_SET)
2850                 memcpy(g.c, term.line[term.c.y][term.c.x].c, UTF_SIZ);
2851
2852         /* remove the old cursor */
2853         if(term.line[oldy][oldx].state & GLYPH_SET) {
2854                 sl = utf8size(term.line[oldy][oldx].c);
2855                 xdraws(term.line[oldy][oldx].c, term.line[oldy][oldx], oldx,
2856                                 oldy, 1, sl);
2857         } else {
2858                 xtermclear(oldx, oldy, oldx, oldy);
2859         }
2860
2861         /* draw the new one */
2862         if(!(IS_SET(MODE_HIDE))) {
2863                 if(!(xw.state & WIN_FOCUSED))
2864                         g.bg = defaultucs;
2865
2866                 if(IS_SET(MODE_REVERSE))
2867                         g.mode |= ATTR_REVERSE, g.fg = defaultcs, g.bg = defaultfg;
2868
2869                 sl = utf8size(g.c);
2870                 xdraws(g.c, g, term.c.x, term.c.y, 1, sl);
2871                 oldx = term.c.x, oldy = term.c.y;
2872         }
2873 }
2874
2875 void
2876 xresettitle(void) {
2877         XStoreName(xw.dpy, xw.win, opt_title ? opt_title : "st");
2878 }
2879
2880 void
2881 redraw(int timeout) {
2882         struct timespec tv = {0, timeout * 1000};
2883
2884         tfulldirt();
2885         draw();
2886
2887         if(timeout > 0) {
2888                 nanosleep(&tv, NULL);
2889                 XSync(xw.dpy, False); /* necessary for a good tput flash */
2890         }
2891 }
2892
2893 void
2894 draw(void) {
2895         XdbeSwapInfo swpinfo[1] = {{xw.win, XdbeCopied}};
2896
2897         drawregion(0, 0, term.col, term.row);
2898         if(usedbe) {
2899                 XdbeSwapBuffers(xw.dpy, swpinfo, 1);
2900         } else {
2901                 XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, xw.w,
2902                                 xw.h, 0, 0);
2903                 XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg].pixel);
2904         }
2905 }
2906
2907 void
2908 drawregion(int x1, int y1, int x2, int y2) {
2909         int ic, ib, x, y, ox, sl;
2910         Glyph base, new;
2911         char buf[DRAW_BUF_SIZ];
2912         bool ena_sel = sel.bx != -1;
2913
2914         if(sel.alt ^ IS_SET(MODE_ALTSCREEN))
2915                 ena_sel = 0;
2916
2917         if(!(xw.state & WIN_VISIBLE))
2918                 return;
2919
2920         for(y = y1; y < y2; y++) {
2921                 if(!term.dirty[y])
2922                         continue;
2923
2924                 xtermclear(0, y, term.col, y);
2925                 term.dirty[y] = 0;
2926                 base = term.line[y][0];
2927                 ic = ib = ox = 0;
2928                 for(x = x1; x < x2; x++) {
2929                         new = term.line[y][x];
2930                         if(ena_sel && *(new.c) && selected(x, y))
2931                                 new.mode ^= ATTR_REVERSE;
2932                         if(ib > 0 && (!(new.state & GLYPH_SET)
2933                                         || ATTRCMP(base, new)
2934                                         || ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
2935                                 xdraws(buf, base, ox, y, ic, ib);
2936                                 ic = ib = 0;
2937                         }
2938                         if(new.state & GLYPH_SET) {
2939                                 if(ib == 0) {
2940                                         ox = x;
2941                                         base = new;
2942                                 }
2943
2944                                 sl = utf8size(new.c);
2945                                 memcpy(buf+ib, new.c, sl);
2946                                 ib += sl;
2947                                 ++ic;
2948                         }
2949                 }
2950                 if(ib > 0)
2951                         xdraws(buf, base, ox, y, ic, ib);
2952         }
2953         xdrawcursor();
2954 }
2955
2956 void
2957 expose(XEvent *ev) {
2958         XExposeEvent *e = &ev->xexpose;
2959
2960         if(xw.state & WIN_REDRAW) {
2961                 if(!e->count)
2962                         xw.state &= ~WIN_REDRAW;
2963         }
2964         redraw(0);
2965 }
2966
2967 void
2968 visibility(XEvent *ev) {
2969         XVisibilityEvent *e = &ev->xvisibility;
2970
2971         if(e->state == VisibilityFullyObscured) {
2972                 xw.state &= ~WIN_VISIBLE;
2973         } else if(!(xw.state & WIN_VISIBLE)) {
2974                 /* need a full redraw for next Expose, not just a buf copy */
2975                 xw.state |= WIN_VISIBLE | WIN_REDRAW;
2976         }
2977 }
2978
2979 void
2980 unmap(XEvent *ev) {
2981         xw.state &= ~WIN_VISIBLE;
2982 }
2983
2984 void
2985 xseturgency(int add) {
2986         XWMHints *h = XGetWMHints(xw.dpy, xw.win);
2987
2988         h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
2989         XSetWMHints(xw.dpy, xw.win, h);
2990         XFree(h);
2991 }
2992
2993 void
2994 focus(XEvent *ev) {
2995         XFocusChangeEvent *e = &ev->xfocus;
2996
2997         if(e->mode == NotifyGrab)
2998                 return;
2999
3000         if(ev->type == FocusIn) {
3001                 XSetICFocus(xw.xic);
3002                 xw.state |= WIN_FOCUSED;
3003                 xseturgency(0);
3004         } else {
3005                 XUnsetICFocus(xw.xic);
3006                 xw.state &= ~WIN_FOCUSED;
3007         }
3008 }
3009
3010 inline bool
3011 match(uint mask, uint state) {
3012         state &= ~(ignoremod);
3013
3014         if(mask == XK_NO_MOD && state)
3015                 return false;
3016         if(mask != XK_ANY_MOD && mask != XK_NO_MOD && !state)
3017                 return false;
3018         if((state & mask) != state)
3019                 return false;
3020         return true;
3021 }
3022
3023 void
3024 numlock(const Arg *dummy) {
3025         term.numlock ^= 1;
3026 }
3027
3028 char*
3029 kmap(KeySym k, uint state) {
3030         uint mask;
3031         Key *kp;
3032         int i;
3033
3034         /* Check for mapped keys out of X11 function keys. */
3035         for(i = 0; i < LEN(mappedkeys); i++) {
3036                 if(mappedkeys[i] == k)
3037                         break;
3038         }
3039         if(i == LEN(mappedkeys)) {
3040                 if((k & 0xFFFF) < 0xFD00)
3041                         return NULL;
3042         }
3043
3044         for(kp = key; kp < key + LEN(key); kp++) {
3045                 mask = kp->mask;
3046
3047                 if(kp->k != k)
3048                         continue;
3049
3050                 if(!match(mask, state))
3051                         continue;
3052
3053                 if(kp->appkey > 0) {
3054                         if(!IS_SET(MODE_APPKEYPAD))
3055                                 continue;
3056                         if(term.numlock && kp->appkey == 2)
3057                                 continue;
3058                 } else if(kp->appkey < 0 && IS_SET(MODE_APPKEYPAD)) {
3059                         continue;
3060                 }
3061
3062                 if((kp->appcursor < 0 && IS_SET(MODE_APPCURSOR)) ||
3063                                 (kp->appcursor > 0
3064                                  && !IS_SET(MODE_APPCURSOR))) {
3065                         continue;
3066                 }
3067
3068                 if((kp->crlf < 0 && IS_SET(MODE_CRLF)) ||
3069                                 (kp->crlf > 0 && !IS_SET(MODE_CRLF))) {
3070                         continue;
3071                 }
3072
3073                 return kp->s;
3074         }
3075
3076         return NULL;
3077 }
3078
3079 void
3080 kpress(XEvent *ev) {
3081         XKeyEvent *e = &ev->xkey;
3082         KeySym ksym;
3083         char xstr[31], buf[32], *customkey, *cp = buf;
3084         int len;
3085         Status status;
3086         Shortcut *bp;
3087
3088         if (IS_SET(MODE_KBDLOCK))
3089                 return;
3090
3091         len = XmbLookupString(xw.xic, e, xstr, sizeof(xstr), &ksym, &status);
3092         e->state &= ~Mod2Mask;
3093         /* 1. shortcuts */
3094         for(bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
3095                 if(ksym == bp->keysym && match(bp->mod, e->state)) {
3096                         bp->func(&(bp->arg));
3097                         return;
3098                 }
3099         }
3100
3101         /* 2. custom keys from config.h */
3102         if((customkey = kmap(ksym, e->state))) {
3103                 len = strlen(customkey);
3104                 memcpy(buf, customkey, len);
3105         /* 2. hardcoded (overrides X lookup) */
3106         } else {
3107                 if(len == 0)
3108                         return;
3109
3110                 if (len == 1 && e->state & Mod1Mask)
3111                         *cp++ = '\033';
3112
3113                 memcpy(cp, xstr, len);
3114                 len = cp - buf + len;
3115         }
3116
3117         ttywrite(buf, len);
3118         if(IS_SET(MODE_ECHO))
3119                 techo(buf, len);
3120 }
3121
3122
3123 void
3124 cmessage(XEvent *e) {
3125         /*
3126          * See xembed specs
3127          *  http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
3128          */
3129         if(e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
3130                 if(e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
3131                         xw.state |= WIN_FOCUSED;
3132                         xseturgency(0);
3133                 } else if(e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
3134                         xw.state &= ~WIN_FOCUSED;
3135                 }
3136         } else if(e->xclient.data.l[0] == xw.wmdeletewin) {
3137                 /* Send SIGHUP to shell */
3138                 kill(pid, SIGHUP);
3139                 exit(EXIT_SUCCESS);
3140         }
3141 }
3142
3143 void
3144 cresize(int width, int height) {
3145         int col, row;
3146
3147         if(width != 0)
3148                 xw.w = width;
3149         if(height != 0)
3150                 xw.h = height;
3151
3152         col = (xw.w - 2 * borderpx) / xw.cw;
3153         row = (xw.h - 2 * borderpx) / xw.ch;
3154
3155         tresize(col, row);
3156         xresize(col, row);
3157         ttyresize();
3158 }
3159
3160 void
3161 resize(XEvent *e) {
3162         if(e->xconfigure.width == xw.w && e->xconfigure.height == xw.h)
3163                 return;
3164
3165         cresize(e->xconfigure.width, e->xconfigure.height);
3166 }
3167
3168 void
3169 run(void) {
3170         XEvent ev;
3171         fd_set rfd;
3172         int xfd = XConnectionNumber(xw.dpy), xev;
3173         struct timeval drawtimeout, *tv = NULL, now, last;
3174
3175         gettimeofday(&last, NULL);
3176
3177         for(xev = actionfps;;) {
3178                 FD_ZERO(&rfd);
3179                 FD_SET(cmdfd, &rfd);
3180                 FD_SET(xfd, &rfd);
3181                 if(select(MAX(xfd, cmdfd)+1, &rfd, NULL, NULL, tv) < 0) {
3182                         if(errno == EINTR)
3183                                 continue;
3184                         die("select failed: %s\n", SERRNO);
3185                 }
3186
3187                 gettimeofday(&now, NULL);
3188                 drawtimeout.tv_sec = 0;
3189                 drawtimeout.tv_usec = (1000/xfps) * 1000;
3190                 tv = &drawtimeout;
3191
3192                 if(FD_ISSET(cmdfd, &rfd))
3193                         ttyread();
3194
3195                 if(FD_ISSET(xfd, &rfd))
3196                         xev = actionfps;
3197
3198                 if(TIMEDIFF(now, last) > \
3199                                 (xev ? (1000/xfps) : (1000/actionfps))) {
3200                         while(XPending(xw.dpy)) {
3201                                 XNextEvent(xw.dpy, &ev);
3202                                 if(XFilterEvent(&ev, None))
3203                                         continue;
3204                                 if(handler[ev.type])
3205                                         (handler[ev.type])(&ev);
3206                         }
3207
3208                         draw();
3209                         XFlush(xw.dpy);
3210                         last = now;
3211
3212                         if(xev && !FD_ISSET(xfd, &rfd))
3213                                 xev--;
3214                         if(!FD_ISSET(cmdfd, &rfd) && !FD_ISSET(xfd, &rfd))
3215                                 tv = NULL;
3216                 }
3217         }
3218 }
3219
3220 int
3221 main(int argc, char *argv[]) {
3222         int i, bitm, xr, yr;
3223         uint wr, hr;
3224
3225         xw.fw = xw.fh = xw.fx = xw.fy = 0;
3226         xw.isfixed = False;
3227
3228         for(i = 1; i < argc; i++) {
3229                 switch(argv[i][0] != '-' || argv[i][2] ? -1 : argv[i][1]) {
3230                 case 'c':
3231                         if(++i < argc)
3232                                 opt_class = argv[i];
3233                         break;
3234                 case 'e':
3235                         /* eat all remaining arguments */
3236                         if(++i < argc)
3237                                 opt_cmd = &argv[i];
3238                         goto run;
3239                 case 'f':
3240                         if(++i < argc)
3241                                 opt_font = argv[i];
3242                         break;
3243                 case 'g':
3244                         if(++i >= argc)
3245                                 break;
3246
3247                         bitm = XParseGeometry(argv[i], &xr, &yr, &wr, &hr);
3248                         if(bitm & XValue)
3249                                 xw.fx = xr;
3250                         if(bitm & YValue)
3251                                 xw.fy = yr;
3252                         if(bitm & WidthValue)
3253                                 xw.fw = (int)wr;
3254                         if(bitm & HeightValue)
3255                                 xw.fh = (int)hr;
3256                         if(bitm & XNegative && xw.fx == 0)
3257                                 xw.fx = -1;
3258                         if(bitm & XNegative && xw.fy == 0)
3259                                 xw.fy = -1;
3260
3261                         if(xw.fh != 0 && xw.fw != 0)
3262                                 xw.isfixed = True;
3263                         break;
3264                 case 'o':
3265                         if(++i < argc)
3266                                 opt_io = argv[i];
3267                         break;
3268                 case 't':
3269                         if(++i < argc)
3270                                 opt_title = argv[i];
3271                         break;
3272                 case 'v':
3273                 default:
3274                         die(USAGE);
3275                 case 'w':
3276                         if(++i < argc)
3277                                 opt_embed = argv[i];
3278                         break;
3279                 }
3280         }
3281
3282 run:
3283         setlocale(LC_CTYPE, "");
3284         XSetLocaleModifiers("");
3285         tnew(80, 24);
3286         xinit();
3287         ttynew();
3288         selinit();
3289         run();
3290
3291         return 0;
3292 }
3293