Typo and the missing incr atom from the patches.
[st.git] / st.c
1 /* See LICENSE for license details. */
2 #include <ctype.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <locale.h>
7 #include <pwd.h>
8 #include <stdarg.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <signal.h>
13 #include <stdint.h>
14 #include <sys/ioctl.h>
15 #include <sys/select.h>
16 #include <sys/stat.h>
17 #include <sys/time.h>
18 #include <sys/types.h>
19 #include <sys/wait.h>
20 #include <time.h>
21 #include <unistd.h>
22 #include <libgen.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/Xft/Xft.h>
29 #include <X11/XKBlib.h>
30 #include <fontconfig/fontconfig.h>
31 #include <wchar.h>
32
33 #include "arg.h"
34
35 char *argv0;
36
37 #define Glyph Glyph_
38 #define Font Font_
39
40 #if   defined(__linux)
41  #include <pty.h>
42 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
43  #include <util.h>
44 #elif defined(__FreeBSD__) || defined(__DragonFly__)
45  #include <libutil.h>
46 #endif
47
48
49 /* XEMBED messages */
50 #define XEMBED_FOCUS_IN  4
51 #define XEMBED_FOCUS_OUT 5
52
53 /* Arbitrary sizes */
54 #define UTF_INVALID   0xFFFD
55 #define UTF_SIZ       4
56 #define ESC_BUF_SIZ   (128*UTF_SIZ)
57 #define ESC_ARG_SIZ   16
58 #define STR_BUF_SIZ   ESC_BUF_SIZ
59 #define STR_ARG_SIZ   ESC_ARG_SIZ
60 #define XK_ANY_MOD    UINT_MAX
61 #define XK_NO_MOD     0
62 #define XK_SWITCH_MOD (1<<13)
63
64 /* macros */
65 #define MIN(a, b)  ((a) < (b) ? (a) : (b))
66 #define MAX(a, b)  ((a) < (b) ? (b) : (a))
67 #define LEN(a)     (sizeof(a) / sizeof(a)[0])
68 #define DEFAULT(a, b)     (a) = (a) ? (a) : (b)
69 #define BETWEEN(x, a, b)  ((a) <= (x) && (x) <= (b))
70 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
71 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
72 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
73 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
74 #define LIMIT(x, a, b)    (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
75 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
76 #define IS_SET(flag) ((term.mode & (flag)) != 0)
77 #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + (t1.tv_nsec-t2.tv_nsec)/1E6)
78 #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
79
80 #define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b))
81 #define IS_TRUECOL(x)    (1 << 24 & (x))
82 #define TRUERED(x)       (((x) & 0xff0000) >> 8)
83 #define TRUEGREEN(x)     (((x) & 0xff00))
84 #define TRUEBLUE(x)      (((x) & 0xff) << 8)
85
86
87 enum glyph_attribute {
88         ATTR_NULL       = 0,
89         ATTR_BOLD       = 1 << 0,
90         ATTR_FAINT      = 1 << 1,
91         ATTR_ITALIC     = 1 << 2,
92         ATTR_UNDERLINE  = 1 << 3,
93         ATTR_BLINK      = 1 << 4,
94         ATTR_REVERSE    = 1 << 5,
95         ATTR_INVISIBLE  = 1 << 6,
96         ATTR_STRUCK     = 1 << 7,
97         ATTR_WRAP       = 1 << 8,
98         ATTR_WIDE       = 1 << 9,
99         ATTR_WDUMMY     = 1 << 10,
100         ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
101 };
102
103 enum cursor_movement {
104         CURSOR_SAVE,
105         CURSOR_LOAD
106 };
107
108 enum cursor_state {
109         CURSOR_DEFAULT  = 0,
110         CURSOR_WRAPNEXT = 1,
111         CURSOR_ORIGIN   = 2
112 };
113
114 enum term_mode {
115         MODE_WRAP        = 1 << 0,
116         MODE_INSERT      = 1 << 1,
117         MODE_APPKEYPAD   = 1 << 2,
118         MODE_ALTSCREEN   = 1 << 3,
119         MODE_CRLF        = 1 << 4,
120         MODE_MOUSEBTN    = 1 << 5,
121         MODE_MOUSEMOTION = 1 << 6,
122         MODE_REVERSE     = 1 << 7,
123         MODE_KBDLOCK     = 1 << 8,
124         MODE_HIDE        = 1 << 9,
125         MODE_ECHO        = 1 << 10,
126         MODE_APPCURSOR   = 1 << 11,
127         MODE_MOUSESGR    = 1 << 12,
128         MODE_8BIT        = 1 << 13,
129         MODE_BLINK       = 1 << 14,
130         MODE_FBLINK      = 1 << 15,
131         MODE_FOCUS       = 1 << 16,
132         MODE_MOUSEX10    = 1 << 17,
133         MODE_MOUSEMANY   = 1 << 18,
134         MODE_BRCKTPASTE  = 1 << 19,
135         MODE_PRINT       = 1 << 20,
136         MODE_MOUSE       = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\
137                           |MODE_MOUSEMANY,
138 };
139
140 enum charset {
141         CS_GRAPHIC0,
142         CS_GRAPHIC1,
143         CS_UK,
144         CS_USA,
145         CS_MULTI,
146         CS_GER,
147         CS_FIN
148 };
149
150 enum escape_state {
151         ESC_START      = 1,
152         ESC_CSI        = 2,
153         ESC_STR        = 4,  /* DCS, OSC, PM, APC */
154         ESC_ALTCHARSET = 8,
155         ESC_STR_END    = 16, /* a final string was encountered */
156         ESC_TEST       = 32, /* Enter in test mode */
157 };
158
159 enum window_state {
160         WIN_VISIBLE = 1,
161         WIN_FOCUSED = 2
162 };
163
164 enum selection_mode {
165         SEL_IDLE = 0,
166         SEL_EMPTY = 1,
167         SEL_READY = 2
168 };
169
170 enum selection_type {
171         SEL_REGULAR = 1,
172         SEL_RECTANGULAR = 2
173 };
174
175 enum selection_snap {
176         SNAP_WORD = 1,
177         SNAP_LINE = 2
178 };
179
180 typedef unsigned char uchar;
181 typedef unsigned int uint;
182 typedef unsigned long ulong;
183 typedef unsigned short ushort;
184
185 typedef uint_least32_t Rune;
186
187 typedef XftDraw *Draw;
188 typedef XftColor Color;
189
190 typedef struct {
191         Rune u;           /* character code */
192         ushort mode;      /* attribute flags */
193         uint32_t fg;      /* foreground  */
194         uint32_t bg;      /* background  */
195 } Glyph;
196
197 typedef Glyph *Line;
198
199 typedef struct {
200         Glyph attr; /* current char attributes */
201         int x;
202         int y;
203         char state;
204 } TCursor;
205
206 /* CSI Escape sequence structs */
207 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
208 typedef struct {
209         char buf[ESC_BUF_SIZ]; /* raw string */
210         int len;               /* raw string length */
211         char priv;
212         int arg[ESC_ARG_SIZ];
213         int narg;              /* nb of args */
214         char mode[2];
215 } CSIEscape;
216
217 /* STR Escape sequence structs */
218 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
219 typedef struct {
220         char type;             /* ESC type ... */
221         char buf[STR_BUF_SIZ]; /* raw string */
222         int len;               /* raw string length */
223         char *args[STR_ARG_SIZ];
224         int narg;              /* nb of args */
225 } STREscape;
226
227 /* Internal representation of the screen */
228 typedef struct {
229         int row;      /* nb row */
230         int col;      /* nb col */
231         Line *line;   /* screen */
232         Line *alt;    /* alternate screen */
233         int *dirty;  /* dirtyness of lines */
234         XftGlyphFontSpec *specbuf; /* font spec buffer used for rendering */
235         TCursor c;    /* cursor */
236         int top;      /* top    scroll limit */
237         int bot;      /* bottom scroll limit */
238         int mode;     /* terminal mode flags */
239         int esc;      /* escape state flags */
240         char trantbl[4]; /* charset table translation */
241         int charset;  /* current charset */
242         int icharset; /* selected charset for sequence */
243         int numlock; /* lock numbers in keyboard */
244         int *tabs;
245 } Term;
246
247 /* Purely graphic info */
248 typedef struct {
249         Display *dpy;
250         Colormap cmap;
251         Window win;
252         Drawable buf;
253         Atom xembed, wmdeletewin, netwmname, netwmpid;
254         XIM xim;
255         XIC xic;
256         Draw draw;
257         Visual *vis;
258         XSetWindowAttributes attrs;
259         int scr;
260         int isfixed; /* is fixed geometry? */
261         int l, t; /* left and top offset */
262         int gm; /* geometry mask */
263         int tw, th; /* tty width and height */
264         int w, h; /* window width and height */
265         int ch; /* char height */
266         int cw; /* char width  */
267         char state; /* focus, redraw, visible */
268         int cursor; /* cursor style */
269 } XWindow;
270
271 typedef struct {
272         uint b;
273         uint mask;
274         char *s;
275 } Mousekey;
276
277 typedef struct {
278         KeySym k;
279         uint mask;
280         char *s;
281         /* three valued logic variables: 0 indifferent, 1 on, -1 off */
282         signed char appkey;    /* application keypad */
283         signed char appcursor; /* application cursor */
284         signed char crlf;      /* crlf mode          */
285 } Key;
286
287 typedef struct {
288         int mode;
289         int type;
290         int snap;
291         /*
292          * Selection variables:
293          * nb – normalized coordinates of the beginning of the selection
294          * ne – normalized coordinates of the end of the selection
295          * ob – original coordinates of the beginning of the selection
296          * oe – original coordinates of the end of the selection
297          */
298         struct {
299                 int x, y;
300         } nb, ne, ob, oe;
301
302         char *primary, *clipboard;
303         Atom xtarget;
304         int alt;
305         struct timespec tclick1;
306         struct timespec tclick2;
307 } Selection;
308
309 typedef union {
310         int i;
311         uint ui;
312         float f;
313         const void *v;
314 } Arg;
315
316 typedef struct {
317         uint mod;
318         KeySym keysym;
319         void (*func)(const Arg *);
320         const Arg arg;
321 } Shortcut;
322
323 /* function definitions used in config.h */
324 static void clipcopy(const Arg *);
325 static void clippaste(const Arg *);
326 static void numlock(const Arg *);
327 static void selpaste(const Arg *);
328 static void xzoom(const Arg *);
329 static void xzoomabs(const Arg *);
330 static void xzoomreset(const Arg *);
331 static void printsel(const Arg *);
332 static void printscreen(const Arg *) ;
333 static void toggleprinter(const Arg *);
334
335 /* Config.h for applying patches and the configuration. */
336 #include "config.h"
337
338 /* Font structure */
339 typedef struct {
340         int height;
341         int width;
342         int ascent;
343         int descent;
344         short lbearing;
345         short rbearing;
346         XftFont *match;
347         FcFontSet *set;
348         FcPattern *pattern;
349 } Font;
350
351 /* Drawing Context */
352 typedef struct {
353         Color col[MAX(LEN(colorname), 256)];
354         Font font, bfont, ifont, ibfont;
355         GC gc;
356 } DC;
357
358 static void die(const char *, ...);
359 static void draw(void);
360 static void redraw(void);
361 static void drawregion(int, int, int, int);
362 static void execsh(void);
363 static void stty(void);
364 static void sigchld(int);
365 static void run(void);
366
367 static void csidump(void);
368 static void csihandle(void);
369 static void csiparse(void);
370 static void csireset(void);
371 static int eschandle(uchar);
372 static void strdump(void);
373 static void strhandle(void);
374 static void strparse(void);
375 static void strreset(void);
376
377 static int tattrset(int);
378 static void tprinter(char *, size_t);
379 static void tdumpsel(void);
380 static void tdumpline(int);
381 static void tdump(void);
382 static void tclearregion(int, int, int, int);
383 static void tcursor(int);
384 static void tdeletechar(int);
385 static void tdeleteline(int);
386 static void tinsertblank(int);
387 static void tinsertblankline(int);
388 static int tlinelen(int);
389 static void tmoveto(int, int);
390 static void tmoveato(int, int);
391 static void tnew(int, int);
392 static void tnewline(int);
393 static void tputtab(int);
394 static void tputc(Rune);
395 static void treset(void);
396 static void tresize(int, int);
397 static void tscrollup(int, int);
398 static void tscrolldown(int, int);
399 static void tsetattr(int *, int);
400 static void tsetchar(Rune, Glyph *, int, int);
401 static void tsetscroll(int, int);
402 static void tswapscreen(void);
403 static void tsetdirt(int, int);
404 static void tsetdirtattr(int);
405 static void tsetmode(int, int, int *, int);
406 static void tfulldirt(void);
407 static void techo(Rune);
408 static void tcontrolcode(uchar );
409 static void tdectest(char );
410 static int32_t tdefcolor(int *, int *, int);
411 static void tdeftran(char);
412 static inline int match(uint, uint);
413 static void ttynew(void);
414 static void ttyread(void);
415 static void ttyresize(void);
416 static void ttysend(char *, size_t);
417 static void ttywrite(const char *, size_t);
418 static void tstrsequence(uchar);
419
420 static inline ushort sixd_to_16bit(int);
421 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
422 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
423 static void xdrawglyph(Glyph, int, int);
424 static void xhints(void);
425 static void xclear(int, int, int, int);
426 static void xdrawcursor(void);
427 static void xinit(void);
428 static void xloadcols(void);
429 static int xsetcolorname(int, const char *);
430 static int xgeommasktogravity(int);
431 static int xloadfont(Font *, FcPattern *);
432 static void xloadfonts(char *, double);
433 static void xsettitle(char *);
434 static void xresettitle(void);
435 static void xsetpointermotion(int);
436 static void xseturgency(int);
437 static void xsetsel(char *, Time);
438 static void xtermclear(int, int, int, int);
439 static void xunloadfont(Font *);
440 static void xunloadfonts(void);
441 static void xresize(int, int);
442
443 static void expose(XEvent *);
444 static void visibility(XEvent *);
445 static void unmap(XEvent *);
446 static char *kmap(KeySym, uint);
447 static void kpress(XEvent *);
448 static void cmessage(XEvent *);
449 static void cresize(int, int);
450 static void resize(XEvent *);
451 static void focus(XEvent *);
452 static void brelease(XEvent *);
453 static void bpress(XEvent *);
454 static void bmotion(XEvent *);
455 static void propnotify(XEvent *);
456 static void selnotify(XEvent *);
457 static void selclear(XEvent *);
458 static void selrequest(XEvent *);
459
460 static void selinit(void);
461 static void selnormalize(void);
462 static inline int selected(int, int);
463 static char *getsel(void);
464 static void selcopy(Time);
465 static void selscroll(int, int);
466 static void selsnap(int *, int *, int);
467 static int x2col(int);
468 static int y2row(int);
469 static void getbuttoninfo(XEvent *);
470 static void mousereport(XEvent *);
471
472 static size_t utf8decode(char *, Rune *, size_t);
473 static Rune utf8decodebyte(char, size_t *);
474 static size_t utf8encode(Rune, char *);
475 static char utf8encodebyte(Rune, size_t);
476 static char *utf8strchr(char *s, Rune u);
477 static size_t utf8validate(Rune *, size_t);
478
479 static ssize_t xwrite(int, const char *, size_t);
480 static void *xmalloc(size_t);
481 static void *xrealloc(void *, size_t);
482 static char *xstrdup(char *);
483
484 static void usage(void);
485
486 static void (*handler[LASTEvent])(XEvent *) = {
487         [KeyPress] = kpress,
488         [ClientMessage] = cmessage,
489         [ConfigureNotify] = resize,
490         [VisibilityNotify] = visibility,
491         [UnmapNotify] = unmap,
492         [Expose] = expose,
493         [FocusIn] = focus,
494         [FocusOut] = focus,
495         [MotionNotify] = bmotion,
496         [ButtonPress] = bpress,
497         [ButtonRelease] = brelease,
498 /*
499  * Uncomment if you want the selection to disappear when you select something
500  * different in another window.
501  */
502 /*      [SelectionClear] = selclear, */
503         [SelectionNotify] = selnotify,
504 /*
505  * PropertyNotify is only turned on when there is some INCR transfer happening
506  * for the selection retrieval.
507  */
508         [PropertyNotify] = propnotify,
509         [SelectionRequest] = selrequest,
510 };
511
512 /* Globals */
513 static DC dc;
514 static XWindow xw;
515 static Term term;
516 static CSIEscape csiescseq;
517 static STREscape strescseq;
518 static int cmdfd;
519 static pid_t pid;
520 static Selection sel;
521 static int iofd = 1;
522 static char **opt_cmd = NULL;
523 static char *opt_io = NULL;
524 static char *opt_title = NULL;
525 static char *opt_embed = NULL;
526 static char *opt_class = NULL;
527 static char *opt_font = NULL;
528 static char *opt_line = NULL;
529 static int oldbutton = 3; /* button event on startup: 3 = release */
530
531 static char *usedfont = NULL;
532 static double usedfontsize = 0;
533 static double defaultfontsize = 0;
534
535 static uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
536 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
537 static Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
538 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
539
540 /* Font Ring Cache */
541 enum {
542         FRC_NORMAL,
543         FRC_ITALIC,
544         FRC_BOLD,
545         FRC_ITALICBOLD
546 };
547
548 typedef struct {
549         XftFont *font;
550         int flags;
551         Rune unicodep;
552 } Fontcache;
553
554 /* Fontcache is an array now. A new font will be appended to the array. */
555 static Fontcache frc[16];
556 static int frclen = 0;
557
558 ssize_t
559 xwrite(int fd, const char *s, size_t len)
560 {
561         size_t aux = len;
562
563         while (len > 0) {
564                 ssize_t r = write(fd, s, len);
565                 if (r < 0)
566                         return r;
567                 len -= r;
568                 s += r;
569         }
570         return aux;
571 }
572
573 void *
574 xmalloc(size_t len)
575 {
576         void *p = malloc(len);
577
578         if (!p)
579                 die("Out of memory\n");
580
581         return p;
582 }
583
584 void *
585 xrealloc(void *p, size_t len)
586 {
587         if ((p = realloc(p, len)) == NULL)
588                 die("Out of memory\n");
589
590         return p;
591 }
592
593 char *
594 xstrdup(char *s)
595 {
596         if ((s = strdup(s)) == NULL)
597                 die("Out of memory\n");
598
599         return s;
600 }
601
602 size_t
603 utf8decode(char *c, Rune *u, size_t clen)
604 {
605         size_t i, j, len, type;
606         Rune udecoded;
607
608         *u = UTF_INVALID;
609         if (!clen)
610                 return 0;
611         udecoded = utf8decodebyte(c[0], &len);
612         if (!BETWEEN(len, 1, UTF_SIZ))
613                 return 1;
614         for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
615                 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
616                 if (type != 0)
617                         return j;
618         }
619         if (j < len)
620                 return 0;
621         *u = udecoded;
622         utf8validate(u, len);
623         return len;
624 }
625
626 Rune
627 utf8decodebyte(char c, size_t *i)
628 {
629         for (*i = 0; *i < LEN(utfmask); ++(*i))
630                 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
631                         return (uchar)c & ~utfmask[*i];
632         return 0;
633 }
634
635 size_t
636 utf8encode(Rune u, char *c)
637 {
638         size_t len, i;
639
640         len = utf8validate(&u, 0);
641         if (len > UTF_SIZ)
642                 return 0;
643         for (i = len - 1; i != 0; --i) {
644                 c[i] = utf8encodebyte(u, 0);
645                 u >>= 6;
646         }
647         c[0] = utf8encodebyte(u, len);
648         return len;
649 }
650
651 char
652 utf8encodebyte(Rune u, size_t i)
653 {
654         return utfbyte[i] | (u & ~utfmask[i]);
655 }
656
657 char *
658 utf8strchr(char *s, Rune u)
659 {
660         Rune r;
661         size_t i, j, len;
662
663         len = strlen(s);
664         for (i = 0, j = 0; i < len; i += j) {
665                 if (!(j = utf8decode(&s[i], &r, len - i)))
666                         break;
667                 if (r == u)
668                         return &(s[i]);
669         }
670         return NULL;
671 }
672
673 size_t
674 utf8validate(Rune *u, size_t i)
675 {
676         if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
677                 *u = UTF_INVALID;
678         for (i = 1; *u > utfmax[i]; ++i)
679                 ;
680         return i;
681 }
682
683 void
684 selinit(void)
685 {
686         memset(&sel.tclick1, 0, sizeof(sel.tclick1));
687         memset(&sel.tclick2, 0, sizeof(sel.tclick2));
688         sel.mode = SEL_IDLE;
689         sel.ob.x = -1;
690         sel.primary = NULL;
691         sel.clipboard = NULL;
692         sel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
693         if (sel.xtarget == None)
694                 sel.xtarget = XA_STRING;
695 }
696
697 int
698 x2col(int x)
699 {
700         x -= borderpx;
701         x /= xw.cw;
702
703         return LIMIT(x, 0, term.col-1);
704 }
705
706 int
707 y2row(int y)
708 {
709         y -= borderpx;
710         y /= xw.ch;
711
712         return LIMIT(y, 0, term.row-1);
713 }
714
715 int
716 tlinelen(int y)
717 {
718         int i = term.col;
719
720         if (term.line[y][i - 1].mode & ATTR_WRAP)
721                 return i;
722
723         while (i > 0 && term.line[y][i - 1].u == ' ')
724                 --i;
725
726         return i;
727 }
728
729 void
730 selnormalize(void)
731 {
732         int i;
733
734         if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
735                 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
736                 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
737         } else {
738                 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
739                 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
740         }
741         sel.nb.y = MIN(sel.ob.y, sel.oe.y);
742         sel.ne.y = MAX(sel.ob.y, sel.oe.y);
743
744         selsnap(&sel.nb.x, &sel.nb.y, -1);
745         selsnap(&sel.ne.x, &sel.ne.y, +1);
746
747         /* expand selection over line breaks */
748         if (sel.type == SEL_RECTANGULAR)
749                 return;
750         i = tlinelen(sel.nb.y);
751         if (i < sel.nb.x)
752                 sel.nb.x = i;
753         if (tlinelen(sel.ne.y) <= sel.ne.x)
754                 sel.ne.x = term.col - 1;
755 }
756
757 int
758 selected(int x, int y)
759 {
760         if (sel.mode == SEL_EMPTY)
761                 return 0;
762
763         if (sel.type == SEL_RECTANGULAR)
764                 return BETWEEN(y, sel.nb.y, sel.ne.y)
765                     && BETWEEN(x, sel.nb.x, sel.ne.x);
766
767         return BETWEEN(y, sel.nb.y, sel.ne.y)
768             && (y != sel.nb.y || x >= sel.nb.x)
769             && (y != sel.ne.y || x <= sel.ne.x);
770 }
771
772 void
773 selsnap(int *x, int *y, int direction)
774 {
775         int newx, newy, xt, yt;
776         int delim, prevdelim;
777         Glyph *gp, *prevgp;
778
779         switch (sel.snap) {
780         case SNAP_WORD:
781                 /*
782                  * Snap around if the word wraps around at the end or
783                  * beginning of a line.
784                  */
785                 prevgp = &term.line[*y][*x];
786                 prevdelim = ISDELIM(prevgp->u);
787                 for (;;) {
788                         newx = *x + direction;
789                         newy = *y;
790                         if (!BETWEEN(newx, 0, term.col - 1)) {
791                                 newy += direction;
792                                 newx = (newx + term.col) % term.col;
793                                 if (!BETWEEN(newy, 0, term.row - 1))
794                                         break;
795
796                                 if (direction > 0)
797                                         yt = *y, xt = *x;
798                                 else
799                                         yt = newy, xt = newx;
800                                 if (!(term.line[yt][xt].mode & ATTR_WRAP))
801                                         break;
802                         }
803
804                         if (newx >= tlinelen(newy))
805                                 break;
806
807                         gp = &term.line[newy][newx];
808                         delim = ISDELIM(gp->u);
809                         if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
810                                         || (delim && gp->u != prevgp->u)))
811                                 break;
812
813                         *x = newx;
814                         *y = newy;
815                         prevgp = gp;
816                         prevdelim = delim;
817                 }
818                 break;
819         case SNAP_LINE:
820                 /*
821                  * Snap around if the the previous line or the current one
822                  * has set ATTR_WRAP at its end. Then the whole next or
823                  * previous line will be selected.
824                  */
825                 *x = (direction < 0) ? 0 : term.col - 1;
826                 if (direction < 0) {
827                         for (; *y > 0; *y += direction) {
828                                 if (!(term.line[*y-1][term.col-1].mode
829                                                 & ATTR_WRAP)) {
830                                         break;
831                                 }
832                         }
833                 } else if (direction > 0) {
834                         for (; *y < term.row-1; *y += direction) {
835                                 if (!(term.line[*y][term.col-1].mode
836                                                 & ATTR_WRAP)) {
837                                         break;
838                                 }
839                         }
840                 }
841                 break;
842         }
843 }
844
845 void
846 getbuttoninfo(XEvent *e)
847 {
848         int type;
849         uint state = e->xbutton.state & ~(Button1Mask | forceselmod);
850
851         sel.alt = IS_SET(MODE_ALTSCREEN);
852
853         sel.oe.x = x2col(e->xbutton.x);
854         sel.oe.y = y2row(e->xbutton.y);
855         selnormalize();
856
857         sel.type = SEL_REGULAR;
858         for (type = 1; type < LEN(selmasks); ++type) {
859                 if (match(selmasks[type], state)) {
860                         sel.type = type;
861                         break;
862                 }
863         }
864 }
865
866 void
867 mousereport(XEvent *e)
868 {
869         int x = x2col(e->xbutton.x), y = y2row(e->xbutton.y),
870             button = e->xbutton.button, state = e->xbutton.state,
871             len;
872         char buf[40];
873         static int ox, oy;
874
875         /* from urxvt */
876         if (e->xbutton.type == MotionNotify) {
877                 if (x == ox && y == oy)
878                         return;
879                 if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY))
880                         return;
881                 /* MOUSE_MOTION: no reporting if no button is pressed */
882                 if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3)
883                         return;
884
885                 button = oldbutton + 32;
886                 ox = x;
887                 oy = y;
888         } else {
889                 if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) {
890                         button = 3;
891                 } else {
892                         button -= Button1;
893                         if (button >= 3)
894                                 button += 64 - 3;
895                 }
896                 if (e->xbutton.type == ButtonPress) {
897                         oldbutton = button;
898                         ox = x;
899                         oy = y;
900                 } else if (e->xbutton.type == ButtonRelease) {
901                         oldbutton = 3;
902                         /* MODE_MOUSEX10: no button release reporting */
903                         if (IS_SET(MODE_MOUSEX10))
904                                 return;
905                         if (button == 64 || button == 65)
906                                 return;
907                 }
908         }
909
910         if (!IS_SET(MODE_MOUSEX10)) {
911                 button += ((state & ShiftMask  ) ? 4  : 0)
912                         + ((state & Mod4Mask   ) ? 8  : 0)
913                         + ((state & ControlMask) ? 16 : 0);
914         }
915
916         if (IS_SET(MODE_MOUSESGR)) {
917                 len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
918                                 button, x+1, y+1,
919                                 e->xbutton.type == ButtonRelease ? 'm' : 'M');
920         } else if (x < 223 && y < 223) {
921                 len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
922                                 32+button, 32+x+1, 32+y+1);
923         } else {
924                 return;
925         }
926
927         ttywrite(buf, len);
928 }
929
930 void
931 bpress(XEvent *e)
932 {
933         struct timespec now;
934         Mousekey *mk;
935
936         if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) {
937                 mousereport(e);
938                 return;
939         }
940
941         for (mk = mshortcuts; mk < mshortcuts + LEN(mshortcuts); mk++) {
942                 if (e->xbutton.button == mk->b
943                                 && match(mk->mask, e->xbutton.state)) {
944                         ttysend(mk->s, strlen(mk->s));
945                         return;
946                 }
947         }
948
949         if (e->xbutton.button == Button1) {
950                 clock_gettime(CLOCK_MONOTONIC, &now);
951
952                 /* Clear previous selection, logically and visually. */
953                 selclear(NULL);
954                 sel.mode = SEL_EMPTY;
955                 sel.type = SEL_REGULAR;
956                 sel.oe.x = sel.ob.x = x2col(e->xbutton.x);
957                 sel.oe.y = sel.ob.y = y2row(e->xbutton.y);
958
959                 /*
960                  * If the user clicks below predefined timeouts specific
961                  * snapping behaviour is exposed.
962                  */
963                 if (TIMEDIFF(now, sel.tclick2) <= tripleclicktimeout) {
964                         sel.snap = SNAP_LINE;
965                 } else if (TIMEDIFF(now, sel.tclick1) <= doubleclicktimeout) {
966                         sel.snap = SNAP_WORD;
967                 } else {
968                         sel.snap = 0;
969                 }
970                 selnormalize();
971
972                 if (sel.snap != 0)
973                         sel.mode = SEL_READY;
974                 tsetdirt(sel.nb.y, sel.ne.y);
975                 sel.tclick2 = sel.tclick1;
976                 sel.tclick1 = now;
977         }
978 }
979
980 char *
981 getsel(void)
982 {
983         char *str, *ptr;
984         int y, bufsize, lastx, linelen;
985         Glyph *gp, *last;
986
987         if (sel.ob.x == -1)
988                 return NULL;
989
990         bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
991         ptr = str = xmalloc(bufsize);
992
993         /* append every set & selected glyph to the selection */
994         for (y = sel.nb.y; y <= sel.ne.y; y++) {
995                 linelen = tlinelen(y);
996
997                 if (sel.type == SEL_RECTANGULAR) {
998                         gp = &term.line[y][sel.nb.x];
999                         lastx = sel.ne.x;
1000                 } else {
1001                         gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
1002                         lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
1003                 }
1004                 last = &term.line[y][MIN(lastx, linelen-1)];
1005                 while (last >= gp && last->u == ' ')
1006                         --last;
1007
1008                 for ( ; gp <= last; ++gp) {
1009                         if (gp->mode & ATTR_WDUMMY)
1010                                 continue;
1011
1012                         ptr += utf8encode(gp->u, ptr);
1013                 }
1014
1015                 /*
1016                  * Copy and pasting of line endings is inconsistent
1017                  * in the inconsistent terminal and GUI world.
1018                  * The best solution seems like to produce '\n' when
1019                  * something is copied from st and convert '\n' to
1020                  * '\r', when something to be pasted is received by
1021                  * st.
1022                  * FIXME: Fix the computer world.
1023                  */
1024                 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
1025                         *ptr++ = '\n';
1026         }
1027         *ptr = 0;
1028         return str;
1029 }
1030
1031 void
1032 selcopy(Time t)
1033 {
1034         xsetsel(getsel(), t);
1035 }
1036
1037 void
1038 propnotify(XEvent *e)
1039 {
1040         XPropertyEvent *xpev;
1041         Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
1042
1043         xpev = &e->xproperty;
1044         if (xpev->state == PropertyNewValue &&
1045                         (xpev->atom == XA_PRIMARY ||
1046                          xpev->atom == clipboard)) {
1047                 selnotify(e);
1048         }
1049 }
1050
1051 void
1052 selnotify(XEvent *e)
1053 {
1054         ulong nitems, ofs, rem;
1055         int format;
1056         uchar *data, *last, *repl;
1057         Atom type, incratom, property;
1058
1059         incratom = XInternAtom(xw.dpy, "INCR", 0);
1060
1061         ofs = 0;
1062         if (e->type == SelectionNotify) {
1063                 property = e->xselection.property;
1064         } else if(e->type == PropertyNotify) {
1065                 property = e->xproperty.atom;
1066         } else {
1067                 return;
1068         }
1069         if (property == None)
1070                 return;
1071
1072         do {
1073                 if (XGetWindowProperty(xw.dpy, xw.win, property, ofs,
1074                                         BUFSIZ/4, False, AnyPropertyType,
1075                                         &type, &format, &nitems, &rem,
1076                                         &data)) {
1077                         fprintf(stderr, "Clipboard allocation failed\n");
1078                         return;
1079                 }
1080
1081                 if (e->type == PropertyNotify && nitems == 0 && rem == 0) {
1082                         /*
1083                          * If there is some PropertyNotify with no data, then
1084                          * this is the signal of the selection owner that all
1085                          * data has been transferred. We won't need to receive
1086                          * PropertyNotify events anymore.
1087                          */
1088                         MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask);
1089                         XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
1090                                         &xw.attrs);
1091                 }
1092
1093                 if (type == incratom) {
1094                         /*
1095                          * Activate the PropertyNotify events so we receive
1096                          * when the selection owner does send us the next
1097                          * chunk of data.
1098                          */
1099                         MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
1100                         XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
1101                                         &xw.attrs);
1102
1103                         /*
1104                          * Deleting the property is the transfer start signal.
1105                          */
1106                         XDeleteProperty(xw.dpy, xw.win, (int)property);
1107                         continue;
1108                 }
1109
1110                 /*
1111                  * As seen in getsel:
1112                  * Line endings are inconsistent in the terminal and GUI world
1113                  * copy and pasting. When receiving some selection data,
1114                  * replace all '\n' with '\r'.
1115                  * FIXME: Fix the computer world.
1116                  */
1117                 repl = data;
1118                 last = data + nitems * format / 8;
1119                 while ((repl = memchr(repl, '\n', last - repl))) {
1120                         *repl++ = '\r';
1121                 }
1122
1123                 if (IS_SET(MODE_BRCKTPASTE))
1124                         ttywrite("\033[200~", 6);
1125                 ttysend((char *)data, nitems * format / 8);
1126                 if (IS_SET(MODE_BRCKTPASTE))
1127                         ttywrite("\033[201~", 6);
1128                 XFree(data);
1129                 /* number of 32-bit chunks returned */
1130                 ofs += nitems * format / 32;
1131         } while (rem > 0);
1132
1133         /*
1134          * Deleting the property again tells the selection owner to send the
1135          * next data chunk in the property.
1136          */
1137         if (e->type == PropertyNotify)
1138                 XDeleteProperty(xw.dpy, xw.win, (int)property);
1139 }
1140
1141 void
1142 selpaste(const Arg *dummy)
1143 {
1144         XConvertSelection(xw.dpy, XA_PRIMARY, sel.xtarget, XA_PRIMARY,
1145                         xw.win, CurrentTime);
1146 }
1147
1148 void
1149 clipcopy(const Arg *dummy)
1150 {
1151         Atom clipboard;
1152
1153         if (sel.clipboard != NULL)
1154                 free(sel.clipboard);
1155
1156         if (sel.primary != NULL) {
1157                 sel.clipboard = xstrdup(sel.primary);
1158                 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
1159                 XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);
1160         }
1161 }
1162
1163 void
1164 clippaste(const Arg *dummy)
1165 {
1166         Atom clipboard;
1167
1168         clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
1169         XConvertSelection(xw.dpy, clipboard, sel.xtarget, clipboard,
1170                         xw.win, CurrentTime);
1171 }
1172
1173 void
1174 selclear(XEvent *e)
1175 {
1176         if (sel.ob.x == -1)
1177                 return;
1178         sel.mode = SEL_IDLE;
1179         sel.ob.x = -1;
1180         tsetdirt(sel.nb.y, sel.ne.y);
1181 }
1182
1183 void
1184 selrequest(XEvent *e)
1185 {
1186         XSelectionRequestEvent *xsre;
1187         XSelectionEvent xev;
1188         Atom xa_targets, string, clipboard;
1189         char *seltext;
1190
1191         xsre = (XSelectionRequestEvent *) e;
1192         xev.type = SelectionNotify;
1193         xev.requestor = xsre->requestor;
1194         xev.selection = xsre->selection;
1195         xev.target = xsre->target;
1196         xev.time = xsre->time;
1197         if (xsre->property == None)
1198                 xsre->property = xsre->target;
1199
1200         /* reject */
1201         xev.property = None;
1202
1203         xa_targets = XInternAtom(xw.dpy, "TARGETS", 0);
1204         if (xsre->target == xa_targets) {
1205                 /* respond with the supported type */
1206                 string = sel.xtarget;
1207                 XChangeProperty(xsre->display, xsre->requestor, xsre->property,
1208                                 XA_ATOM, 32, PropModeReplace,
1209                                 (uchar *) &string, 1);
1210                 xev.property = xsre->property;
1211         } else if (xsre->target == sel.xtarget || xsre->target == XA_STRING) {
1212                 /*
1213                  * xith XA_STRING non ascii characters may be incorrect in the
1214                  * requestor. It is not our problem, use utf8.
1215                  */
1216                 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
1217                 if (xsre->selection == XA_PRIMARY) {
1218                         seltext = sel.primary;
1219                 } else if (xsre->selection == clipboard) {
1220                         seltext = sel.clipboard;
1221                 } else {
1222                         fprintf(stderr,
1223                                 "Unhandled clipboard selection 0x%lx\n",
1224                                 xsre->selection);
1225                         return;
1226                 }
1227                 if (seltext != NULL) {
1228                         XChangeProperty(xsre->display, xsre->requestor,
1229                                         xsre->property, xsre->target,
1230                                         8, PropModeReplace,
1231                                         (uchar *)seltext, strlen(seltext));
1232                         xev.property = xsre->property;
1233                 }
1234         }
1235
1236         /* all done, send a notification to the listener */
1237         if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev))
1238                 fprintf(stderr, "Error sending SelectionNotify event\n");
1239 }
1240
1241 void
1242 xsetsel(char *str, Time t)
1243 {
1244         free(sel.primary);
1245         sel.primary = str;
1246
1247         XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);
1248         if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)
1249                 selclear(0);
1250 }
1251
1252 void
1253 brelease(XEvent *e)
1254 {
1255         if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) {
1256                 mousereport(e);
1257                 return;
1258         }
1259
1260         if (e->xbutton.button == Button2) {
1261                 selpaste(NULL);
1262         } else if (e->xbutton.button == Button1) {
1263                 if (sel.mode == SEL_READY) {
1264                         getbuttoninfo(e);
1265                         selcopy(e->xbutton.time);
1266                 } else
1267                         selclear(NULL);
1268                 sel.mode = SEL_IDLE;
1269                 tsetdirt(sel.nb.y, sel.ne.y);
1270         }
1271 }
1272
1273 void
1274 bmotion(XEvent *e)
1275 {
1276         int oldey, oldex, oldsby, oldsey;
1277
1278         if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) {
1279                 mousereport(e);
1280                 return;
1281         }
1282
1283         if (!sel.mode)
1284                 return;
1285
1286         sel.mode = SEL_READY;
1287         oldey = sel.oe.y;
1288         oldex = sel.oe.x;
1289         oldsby = sel.nb.y;
1290         oldsey = sel.ne.y;
1291         getbuttoninfo(e);
1292
1293         if (oldey != sel.oe.y || oldex != sel.oe.x)
1294                 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
1295 }
1296
1297 void
1298 die(const char *errstr, ...)
1299 {
1300         va_list ap;
1301
1302         va_start(ap, errstr);
1303         vfprintf(stderr, errstr, ap);
1304         va_end(ap);
1305         exit(1);
1306 }
1307
1308 void
1309 execsh(void)
1310 {
1311         char **args, *sh, *prog;
1312         const struct passwd *pw;
1313         char buf[sizeof(long) * 8 + 1];
1314
1315         errno = 0;
1316         if ((pw = getpwuid(getuid())) == NULL) {
1317                 if (errno)
1318                         die("getpwuid:%s\n", strerror(errno));
1319                 else
1320                         die("who are you?\n");
1321         }
1322
1323         if (!(sh = getenv("SHELL"))) {
1324                 sh = (pw->pw_shell[0]) ? pw->pw_shell : shell;
1325         }
1326
1327         if (opt_cmd)
1328                 prog = opt_cmd[0];
1329         else if (utmp)
1330                 prog = utmp;
1331         else
1332                 prog = sh;
1333         args = (opt_cmd) ? opt_cmd : (char *[]) {prog, NULL};
1334
1335         snprintf(buf, sizeof(buf), "%lu", xw.win);
1336
1337         unsetenv("COLUMNS");
1338         unsetenv("LINES");
1339         unsetenv("TERMCAP");
1340         setenv("LOGNAME", pw->pw_name, 1);
1341         setenv("USER", pw->pw_name, 1);
1342         setenv("SHELL", sh, 1);
1343         setenv("HOME", pw->pw_dir, 1);
1344         setenv("TERM", termname, 1);
1345         setenv("WINDOWID", buf, 1);
1346
1347         signal(SIGCHLD, SIG_DFL);
1348         signal(SIGHUP, SIG_DFL);
1349         signal(SIGINT, SIG_DFL);
1350         signal(SIGQUIT, SIG_DFL);
1351         signal(SIGTERM, SIG_DFL);
1352         signal(SIGALRM, SIG_DFL);
1353
1354         execvp(prog, args);
1355         _exit(1);
1356 }
1357
1358 void
1359 sigchld(int a)
1360 {
1361         int stat;
1362         pid_t p;
1363
1364         if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
1365                 die("Waiting for pid %hd failed: %s\n", pid, strerror(errno));
1366
1367         if (pid != p)
1368                 return;
1369
1370         if (!WIFEXITED(stat) || WEXITSTATUS(stat))
1371                 die("child finished with error '%d'\n", stat);
1372         exit(0);
1373 }
1374
1375
1376 void
1377 stty(void)
1378 {
1379         char cmd[_POSIX_ARG_MAX], **p, *q, *s;
1380         size_t n, siz;
1381
1382         if ((n = strlen(stty_args)) > sizeof(cmd)-1)
1383                 die("incorrect stty parameters\n");
1384         memcpy(cmd, stty_args, n);
1385         q = cmd + n;
1386         siz = sizeof(cmd) - n;
1387         for (p = opt_cmd; p && (s = *p); ++p) {
1388                 if ((n = strlen(s)) > siz-1)
1389                         die("stty parameter length too long\n");
1390                 *q++ = ' ';
1391                 q = memcpy(q, s, n);
1392                 q += n;
1393                 siz-= n + 1;
1394         }
1395         *q = '\0';
1396         if (system(cmd) != 0)
1397             perror("Couldn't call stty");
1398 }
1399
1400 void
1401 ttynew(void)
1402 {
1403         int m, s;
1404         struct winsize w = {term.row, term.col, 0, 0};
1405
1406         if (opt_io) {
1407                 term.mode |= MODE_PRINT;
1408                 iofd = (!strcmp(opt_io, "-")) ?
1409                           1 : open(opt_io, O_WRONLY | O_CREAT, 0666);
1410                 if (iofd < 0) {
1411                         fprintf(stderr, "Error opening %s:%s\n",
1412                                 opt_io, strerror(errno));
1413                 }
1414         }
1415
1416         if (opt_line) {
1417                 if ((cmdfd = open(opt_line, O_RDWR)) < 0)
1418                         die("open line failed: %s\n", strerror(errno));
1419                 close(0);
1420                 dup(cmdfd);
1421                 stty();
1422                 return;
1423         }
1424
1425         /* seems to work fine on linux, openbsd and freebsd */
1426         if (openpty(&m, &s, NULL, NULL, &w) < 0)
1427                 die("openpty failed: %s\n", strerror(errno));
1428
1429         switch (pid = fork()) {
1430         case -1:
1431                 die("fork failed\n");
1432                 break;
1433         case 0:
1434                 close(iofd);
1435                 setsid(); /* create a new process group */
1436                 dup2(s, 0);
1437                 dup2(s, 1);
1438                 dup2(s, 2);
1439                 if (ioctl(s, TIOCSCTTY, NULL) < 0)
1440                         die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
1441                 close(s);
1442                 close(m);
1443                 execsh();
1444                 break;
1445         default:
1446                 close(s);
1447                 cmdfd = m;
1448                 signal(SIGCHLD, sigchld);
1449                 break;
1450         }
1451 }
1452
1453 void
1454 ttyread(void)
1455 {
1456         static char buf[BUFSIZ];
1457         static int buflen = 0;
1458         char *ptr;
1459         int charsize; /* size of utf8 char in bytes */
1460         Rune unicodep;
1461         int ret;
1462
1463         /* append read bytes to unprocessed bytes */
1464         if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
1465                 die("Couldn't read from shell: %s\n", strerror(errno));
1466
1467         /* process every complete utf8 char */
1468         buflen += ret;
1469         ptr = buf;
1470         while ((charsize = utf8decode(ptr, &unicodep, buflen))) {
1471                 tputc(unicodep);
1472                 ptr += charsize;
1473                 buflen -= charsize;
1474         }
1475
1476         /* keep any uncomplete utf8 char for the next call */
1477         memmove(buf, ptr, buflen);
1478 }
1479
1480 void
1481 ttywrite(const char *s, size_t n)
1482 {
1483         fd_set wfd;
1484         struct timespec tv;
1485         ssize_t r;
1486
1487         /*
1488          * Remember that we are using a pty, which might be a modem line.
1489          * Writing too much will clog the line. That's why we are doing this
1490          * dance.
1491          * FIXME: Migrate the world to Plan 9.
1492          */
1493         while (n > 0) {
1494                 FD_ZERO(&wfd);
1495                 FD_SET(cmdfd, &wfd);
1496                 tv.tv_sec = 0;
1497                 tv.tv_nsec = 0;
1498
1499                 /* Check if we can write. */
1500                 if (pselect(cmdfd+1, NULL, &wfd, NULL, &tv, NULL) < 0) {
1501                         if (errno == EINTR)
1502                                 continue;
1503                         die("select failed: %s\n", strerror(errno));
1504                 }
1505                 if(!FD_ISSET(cmdfd, &wfd)) {
1506                         /* No, then free some buffer space. */
1507                         ttyread();
1508                 } else {
1509                         /*
1510                          * Only write 256 bytes at maximum. This seems to be a
1511                          * reasonable value for a serial line. Bigger values
1512                          * might clog the I/O.
1513                          */
1514                         r = write(cmdfd, s, (n < 256)? n : 256);
1515                         if (r < 0) {
1516                                 die("write error on tty: %s\n",
1517                                                 strerror(errno));
1518                         }
1519                         if (r < n) {
1520                                 /*
1521                                  * We weren't able to write out everything.
1522                                  * This means the buffer is getting full
1523                                  * again. Empty it.
1524                                  */
1525                                 ttyread();
1526                                 n -= r;
1527                                 s += r;
1528                         } else {
1529                                 /* All bytes have been written. */
1530                                 break;
1531                         }
1532                 }
1533         }
1534 }
1535
1536 void
1537 ttysend(char *s, size_t n)
1538 {
1539         int len;
1540         Rune u;
1541
1542         ttywrite(s, n);
1543         if (IS_SET(MODE_ECHO))
1544                 while ((len = utf8decode(s, &u, n)) > 0) {
1545                         techo(u);
1546                         n -= len;
1547                         s += len;
1548                 }
1549 }
1550
1551 void
1552 ttyresize(void)
1553 {
1554         struct winsize w;
1555
1556         w.ws_row = term.row;
1557         w.ws_col = term.col;
1558         w.ws_xpixel = xw.tw;
1559         w.ws_ypixel = xw.th;
1560         if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
1561                 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
1562 }
1563
1564 int
1565 tattrset(int attr)
1566 {
1567         int i, j;
1568
1569         for (i = 0; i < term.row-1; i++) {
1570                 for (j = 0; j < term.col-1; j++) {
1571                         if (term.line[i][j].mode & attr)
1572                                 return 1;
1573                 }
1574         }
1575
1576         return 0;
1577 }
1578
1579 void
1580 tsetdirt(int top, int bot)
1581 {
1582         int i;
1583
1584         LIMIT(top, 0, term.row-1);
1585         LIMIT(bot, 0, term.row-1);
1586
1587         for (i = top; i <= bot; i++)
1588                 term.dirty[i] = 1;
1589 }
1590
1591 void
1592 tsetdirtattr(int attr)
1593 {
1594         int i, j;
1595
1596         for (i = 0; i < term.row-1; i++) {
1597                 for (j = 0; j < term.col-1; j++) {
1598                         if (term.line[i][j].mode & attr) {
1599                                 tsetdirt(i, i);
1600                                 break;
1601                         }
1602                 }
1603         }
1604 }
1605
1606 void
1607 tfulldirt(void)
1608 {
1609         tsetdirt(0, term.row-1);
1610 }
1611
1612 void
1613 tcursor(int mode)
1614 {
1615         static TCursor c[2];
1616         int alt = IS_SET(MODE_ALTSCREEN);
1617
1618         if (mode == CURSOR_SAVE) {
1619                 c[alt] = term.c;
1620         } else if (mode == CURSOR_LOAD) {
1621                 term.c = c[alt];
1622                 tmoveto(c[alt].x, c[alt].y);
1623         }
1624 }
1625
1626 void
1627 treset(void)
1628 {
1629         uint i;
1630
1631         term.c = (TCursor){{
1632                 .mode = ATTR_NULL,
1633                 .fg = defaultfg,
1634                 .bg = defaultbg
1635         }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1636
1637         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1638         for (i = tabspaces; i < term.col; i += tabspaces)
1639                 term.tabs[i] = 1;
1640         term.top = 0;
1641         term.bot = term.row - 1;
1642         term.mode = MODE_WRAP;
1643         memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1644         term.charset = 0;
1645
1646         for (i = 0; i < 2; i++) {
1647                 tmoveto(0, 0);
1648                 tcursor(CURSOR_SAVE);
1649                 tclearregion(0, 0, term.col-1, term.row-1);
1650                 tswapscreen();
1651         }
1652 }
1653
1654 void
1655 tnew(int col, int row)
1656 {
1657         term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1658         tresize(col, row);
1659         term.numlock = 1;
1660
1661         treset();
1662 }
1663
1664 void
1665 tswapscreen(void)
1666 {
1667         Line *tmp = term.line;
1668
1669         term.line = term.alt;
1670         term.alt = tmp;
1671         term.mode ^= MODE_ALTSCREEN;
1672         tfulldirt();
1673 }
1674
1675 void
1676 tscrolldown(int orig, int n)
1677 {
1678         int i;
1679         Line temp;
1680
1681         LIMIT(n, 0, term.bot-orig+1);
1682
1683         tsetdirt(orig, term.bot-n);
1684         tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1685
1686         for (i = term.bot; i >= orig+n; i--) {
1687                 temp = term.line[i];
1688                 term.line[i] = term.line[i-n];
1689                 term.line[i-n] = temp;
1690         }
1691
1692         selscroll(orig, n);
1693 }
1694
1695 void
1696 tscrollup(int orig, int n)
1697 {
1698         int i;
1699         Line temp;
1700
1701         LIMIT(n, 0, term.bot-orig+1);
1702
1703         tclearregion(0, orig, term.col-1, orig+n-1);
1704         tsetdirt(orig+n, term.bot);
1705
1706         for (i = orig; i <= term.bot-n; i++) {
1707                 temp = term.line[i];
1708                 term.line[i] = term.line[i+n];
1709                 term.line[i+n] = temp;
1710         }
1711
1712         selscroll(orig, -n);
1713 }
1714
1715 void
1716 selscroll(int orig, int n)
1717 {
1718         if (sel.ob.x == -1)
1719                 return;
1720
1721         if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1722                 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1723                         selclear(NULL);
1724                         return;
1725                 }
1726                 if (sel.type == SEL_RECTANGULAR) {
1727                         if (sel.ob.y < term.top)
1728                                 sel.ob.y = term.top;
1729                         if (sel.oe.y > term.bot)
1730                                 sel.oe.y = term.bot;
1731                 } else {
1732                         if (sel.ob.y < term.top) {
1733                                 sel.ob.y = term.top;
1734                                 sel.ob.x = 0;
1735                         }
1736                         if (sel.oe.y > term.bot) {
1737                                 sel.oe.y = term.bot;
1738                                 sel.oe.x = term.col;
1739                         }
1740                 }
1741                 selnormalize();
1742         }
1743 }
1744
1745 void
1746 tnewline(int first_col)
1747 {
1748         int y = term.c.y;
1749
1750         if (y == term.bot) {
1751                 tscrollup(term.top, 1);
1752         } else {
1753                 y++;
1754         }
1755         tmoveto(first_col ? 0 : term.c.x, y);
1756 }
1757
1758 void
1759 csiparse(void)
1760 {
1761         char *p = csiescseq.buf, *np;
1762         long int v;
1763
1764         csiescseq.narg = 0;
1765         if (*p == '?') {
1766                 csiescseq.priv = 1;
1767                 p++;
1768         }
1769
1770         csiescseq.buf[csiescseq.len] = '\0';
1771         while (p < csiescseq.buf+csiescseq.len) {
1772                 np = NULL;
1773                 v = strtol(p, &np, 10);
1774                 if (np == p)
1775                         v = 0;
1776                 if (v == LONG_MAX || v == LONG_MIN)
1777                         v = -1;
1778                 csiescseq.arg[csiescseq.narg++] = v;
1779                 p = np;
1780                 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1781                         break;
1782                 p++;
1783         }
1784         csiescseq.mode[0] = *p++;
1785         csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1786 }
1787
1788 /* for absolute user moves, when decom is set */
1789 void
1790 tmoveato(int x, int y)
1791 {
1792         tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1793 }
1794
1795 void
1796 tmoveto(int x, int y)
1797 {
1798         int miny, maxy;
1799
1800         if (term.c.state & CURSOR_ORIGIN) {
1801                 miny = term.top;
1802                 maxy = term.bot;
1803         } else {
1804                 miny = 0;
1805                 maxy = term.row - 1;
1806         }
1807         term.c.state &= ~CURSOR_WRAPNEXT;
1808         term.c.x = LIMIT(x, 0, term.col-1);
1809         term.c.y = LIMIT(y, miny, maxy);
1810 }
1811
1812 void
1813 tsetchar(Rune u, Glyph *attr, int x, int y)
1814 {
1815         static char *vt100_0[62] = { /* 0x41 - 0x7e */
1816                 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1817                 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1818                 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1819                 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1820                 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1821                 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1822                 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1823                 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1824         };
1825
1826         /*
1827          * The table is proudly stolen from rxvt.
1828          */
1829         if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1830            BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1831                 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1832
1833         if (term.line[y][x].mode & ATTR_WIDE) {
1834                 if (x+1 < term.col) {
1835                         term.line[y][x+1].u = ' ';
1836                         term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1837                 }
1838         } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1839                 term.line[y][x-1].u = ' ';
1840                 term.line[y][x-1].mode &= ~ATTR_WIDE;
1841         }
1842
1843         term.dirty[y] = 1;
1844         term.line[y][x] = *attr;
1845         term.line[y][x].u = u;
1846 }
1847
1848 void
1849 tclearregion(int x1, int y1, int x2, int y2)
1850 {
1851         int x, y, temp;
1852         Glyph *gp;
1853
1854         if (x1 > x2)
1855                 temp = x1, x1 = x2, x2 = temp;
1856         if (y1 > y2)
1857                 temp = y1, y1 = y2, y2 = temp;
1858
1859         LIMIT(x1, 0, term.col-1);
1860         LIMIT(x2, 0, term.col-1);
1861         LIMIT(y1, 0, term.row-1);
1862         LIMIT(y2, 0, term.row-1);
1863
1864         for (y = y1; y <= y2; y++) {
1865                 term.dirty[y] = 1;
1866                 for (x = x1; x <= x2; x++) {
1867                         gp = &term.line[y][x];
1868                         if (selected(x, y))
1869                                 selclear(NULL);
1870                         gp->fg = term.c.attr.fg;
1871                         gp->bg = term.c.attr.bg;
1872                         gp->mode = 0;
1873                         gp->u = ' ';
1874                 }
1875         }
1876 }
1877
1878 void
1879 tdeletechar(int n)
1880 {
1881         int dst, src, size;
1882         Glyph *line;
1883
1884         LIMIT(n, 0, term.col - term.c.x);
1885
1886         dst = term.c.x;
1887         src = term.c.x + n;
1888         size = term.col - src;
1889         line = term.line[term.c.y];
1890
1891         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1892         tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1893 }
1894
1895 void
1896 tinsertblank(int n)
1897 {
1898         int dst, src, size;
1899         Glyph *line;
1900
1901         LIMIT(n, 0, term.col - term.c.x);
1902
1903         dst = term.c.x + n;
1904         src = term.c.x;
1905         size = term.col - dst;
1906         line = term.line[term.c.y];
1907
1908         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1909         tclearregion(src, term.c.y, dst - 1, term.c.y);
1910 }
1911
1912 void
1913 tinsertblankline(int n)
1914 {
1915         if (BETWEEN(term.c.y, term.top, term.bot))
1916                 tscrolldown(term.c.y, n);
1917 }
1918
1919 void
1920 tdeleteline(int n)
1921 {
1922         if (BETWEEN(term.c.y, term.top, term.bot))
1923                 tscrollup(term.c.y, n);
1924 }
1925
1926 int32_t
1927 tdefcolor(int *attr, int *npar, int l)
1928 {
1929         int32_t idx = -1;
1930         uint r, g, b;
1931
1932         switch (attr[*npar + 1]) {
1933         case 2: /* direct color in RGB space */
1934                 if (*npar + 4 >= l) {
1935                         fprintf(stderr,
1936                                 "erresc(38): Incorrect number of parameters (%d)\n",
1937                                 *npar);
1938                         break;
1939                 }
1940                 r = attr[*npar + 2];
1941                 g = attr[*npar + 3];
1942                 b = attr[*npar + 4];
1943                 *npar += 4;
1944                 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1945                         fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1946                                 r, g, b);
1947                 else
1948                         idx = TRUECOLOR(r, g, b);
1949                 break;
1950         case 5: /* indexed color */
1951                 if (*npar + 2 >= l) {
1952                         fprintf(stderr,
1953                                 "erresc(38): Incorrect number of parameters (%d)\n",
1954                                 *npar);
1955                         break;
1956                 }
1957                 *npar += 2;
1958                 if (!BETWEEN(attr[*npar], 0, 255))
1959                         fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1960                 else
1961                         idx = attr[*npar];
1962                 break;
1963         case 0: /* implemented defined (only foreground) */
1964         case 1: /* transparent */
1965         case 3: /* direct color in CMY space */
1966         case 4: /* direct color in CMYK space */
1967         default:
1968                 fprintf(stderr,
1969                         "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1970                 break;
1971         }
1972
1973         return idx;
1974 }
1975
1976 void
1977 tsetattr(int *attr, int l)
1978 {
1979         int i;
1980         int32_t idx;
1981
1982         for (i = 0; i < l; i++) {
1983                 switch (attr[i]) {
1984                 case 0:
1985                         term.c.attr.mode &= ~(
1986                                 ATTR_BOLD       |
1987                                 ATTR_FAINT      |
1988                                 ATTR_ITALIC     |
1989                                 ATTR_UNDERLINE  |
1990                                 ATTR_BLINK      |
1991                                 ATTR_REVERSE    |
1992                                 ATTR_INVISIBLE  |
1993                                 ATTR_STRUCK     );
1994                         term.c.attr.fg = defaultfg;
1995                         term.c.attr.bg = defaultbg;
1996                         break;
1997                 case 1:
1998                         term.c.attr.mode |= ATTR_BOLD;
1999                         break;
2000                 case 2:
2001                         term.c.attr.mode |= ATTR_FAINT;
2002                         break;
2003                 case 3:
2004                         term.c.attr.mode |= ATTR_ITALIC;
2005                         break;
2006                 case 4:
2007                         term.c.attr.mode |= ATTR_UNDERLINE;
2008                         break;
2009                 case 5: /* slow blink */
2010                         /* FALLTHROUGH */
2011                 case 6: /* rapid blink */
2012                         term.c.attr.mode |= ATTR_BLINK;
2013                         break;
2014                 case 7:
2015                         term.c.attr.mode |= ATTR_REVERSE;
2016                         break;
2017                 case 8:
2018                         term.c.attr.mode |= ATTR_INVISIBLE;
2019                         break;
2020                 case 9:
2021                         term.c.attr.mode |= ATTR_STRUCK;
2022                         break;
2023                 case 22:
2024                         term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
2025                         break;
2026                 case 23:
2027                         term.c.attr.mode &= ~ATTR_ITALIC;
2028                         break;
2029                 case 24:
2030                         term.c.attr.mode &= ~ATTR_UNDERLINE;
2031                         break;
2032                 case 25:
2033                         term.c.attr.mode &= ~ATTR_BLINK;
2034                         break;
2035                 case 27:
2036                         term.c.attr.mode &= ~ATTR_REVERSE;
2037                         break;
2038                 case 28:
2039                         term.c.attr.mode &= ~ATTR_INVISIBLE;
2040                         break;
2041                 case 29:
2042                         term.c.attr.mode &= ~ATTR_STRUCK;
2043                         break;
2044                 case 38:
2045                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
2046                                 term.c.attr.fg = idx;
2047                         break;
2048                 case 39:
2049                         term.c.attr.fg = defaultfg;
2050                         break;
2051                 case 48:
2052                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
2053                                 term.c.attr.bg = idx;
2054                         break;
2055                 case 49:
2056                         term.c.attr.bg = defaultbg;
2057                         break;
2058                 default:
2059                         if (BETWEEN(attr[i], 30, 37)) {
2060                                 term.c.attr.fg = attr[i] - 30;
2061                         } else if (BETWEEN(attr[i], 40, 47)) {
2062                                 term.c.attr.bg = attr[i] - 40;
2063                         } else if (BETWEEN(attr[i], 90, 97)) {
2064                                 term.c.attr.fg = attr[i] - 90 + 8;
2065                         } else if (BETWEEN(attr[i], 100, 107)) {
2066                                 term.c.attr.bg = attr[i] - 100 + 8;
2067                         } else {
2068                                 fprintf(stderr,
2069                                         "erresc(default): gfx attr %d unknown\n",
2070                                         attr[i]), csidump();
2071                         }
2072                         break;
2073                 }
2074         }
2075 }
2076
2077 void
2078 tsetscroll(int t, int b)
2079 {
2080         int temp;
2081
2082         LIMIT(t, 0, term.row-1);
2083         LIMIT(b, 0, term.row-1);
2084         if (t > b) {
2085                 temp = t;
2086                 t = b;
2087                 b = temp;
2088         }
2089         term.top = t;
2090         term.bot = b;
2091 }
2092
2093 void
2094 tsetmode(int priv, int set, int *args, int narg)
2095 {
2096         int *lim, mode;
2097         int alt;
2098
2099         for (lim = args + narg; args < lim; ++args) {
2100                 if (priv) {
2101                         switch (*args) {
2102                         case 1: /* DECCKM -- Cursor key */
2103                                 MODBIT(term.mode, set, MODE_APPCURSOR);
2104                                 break;
2105                         case 5: /* DECSCNM -- Reverse video */
2106                                 mode = term.mode;
2107                                 MODBIT(term.mode, set, MODE_REVERSE);
2108                                 if (mode != term.mode)
2109                                         redraw();
2110                                 break;
2111                         case 6: /* DECOM -- Origin */
2112                                 MODBIT(term.c.state, set, CURSOR_ORIGIN);
2113                                 tmoveato(0, 0);
2114                                 break;
2115                         case 7: /* DECAWM -- Auto wrap */
2116                                 MODBIT(term.mode, set, MODE_WRAP);
2117                                 break;
2118                         case 0:  /* Error (IGNORED) */
2119                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
2120                         case 3:  /* DECCOLM -- Column  (IGNORED) */
2121                         case 4:  /* DECSCLM -- Scroll (IGNORED) */
2122                         case 8:  /* DECARM -- Auto repeat (IGNORED) */
2123                         case 18: /* DECPFF -- Printer feed (IGNORED) */
2124                         case 19: /* DECPEX -- Printer extent (IGNORED) */
2125                         case 42: /* DECNRCM -- National characters (IGNORED) */
2126                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
2127                                 break;
2128                         case 25: /* DECTCEM -- Text Cursor Enable Mode */
2129                                 MODBIT(term.mode, !set, MODE_HIDE);
2130                                 break;
2131                         case 9:    /* X10 mouse compatibility mode */
2132                                 xsetpointermotion(0);
2133                                 MODBIT(term.mode, 0, MODE_MOUSE);
2134                                 MODBIT(term.mode, set, MODE_MOUSEX10);
2135                                 break;
2136                         case 1000: /* 1000: report button press */
2137                                 xsetpointermotion(0);
2138                                 MODBIT(term.mode, 0, MODE_MOUSE);
2139                                 MODBIT(term.mode, set, MODE_MOUSEBTN);
2140                                 break;
2141                         case 1002: /* 1002: report motion on button press */
2142                                 xsetpointermotion(0);
2143                                 MODBIT(term.mode, 0, MODE_MOUSE);
2144                                 MODBIT(term.mode, set, MODE_MOUSEMOTION);
2145                                 break;
2146                         case 1003: /* 1003: enable all mouse motions */
2147                                 xsetpointermotion(set);
2148                                 MODBIT(term.mode, 0, MODE_MOUSE);
2149                                 MODBIT(term.mode, set, MODE_MOUSEMANY);
2150                                 break;
2151                         case 1004: /* 1004: send focus events to tty */
2152                                 MODBIT(term.mode, set, MODE_FOCUS);
2153                                 break;
2154                         case 1006: /* 1006: extended reporting mode */
2155                                 MODBIT(term.mode, set, MODE_MOUSESGR);
2156                                 break;
2157                         case 1034:
2158                                 MODBIT(term.mode, set, MODE_8BIT);
2159                                 break;
2160                         case 1049: /* swap screen & set/restore cursor as xterm */
2161                                 if (!allowaltscreen)
2162                                         break;
2163                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
2164                                 /* FALLTHROUGH */
2165                         case 47: /* swap screen */
2166                         case 1047:
2167                                 if (!allowaltscreen)
2168                                         break;
2169                                 alt = IS_SET(MODE_ALTSCREEN);
2170                                 if (alt) {
2171                                         tclearregion(0, 0, term.col-1,
2172                                                         term.row-1);
2173                                 }
2174                                 if (set ^ alt) /* set is always 1 or 0 */
2175                                         tswapscreen();
2176                                 if (*args != 1049)
2177                                         break;
2178                                 /* FALLTHROUGH */
2179                         case 1048:
2180                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
2181                                 break;
2182                         case 2004: /* 2004: bracketed paste mode */
2183                                 MODBIT(term.mode, set, MODE_BRCKTPASTE);
2184                                 break;
2185                         /* Not implemented mouse modes. See comments there. */
2186                         case 1001: /* mouse highlight mode; can hang the
2187                                       terminal by design when implemented. */
2188                         case 1005: /* UTF-8 mouse mode; will confuse
2189                                       applications not supporting UTF-8
2190                                       and luit. */
2191                         case 1015: /* urxvt mangled mouse mode; incompatible
2192                                       and can be mistaken for other control
2193                                       codes. */
2194                         default:
2195                                 fprintf(stderr,
2196                                         "erresc: unknown private set/reset mode %d\n",
2197                                         *args);
2198                                 break;
2199                         }
2200                 } else {
2201                         switch (*args) {
2202                         case 0:  /* Error (IGNORED) */
2203                                 break;
2204                         case 2:  /* KAM -- keyboard action */
2205                                 MODBIT(term.mode, set, MODE_KBDLOCK);
2206                                 break;
2207                         case 4:  /* IRM -- Insertion-replacement */
2208                                 MODBIT(term.mode, set, MODE_INSERT);
2209                                 break;
2210                         case 12: /* SRM -- Send/Receive */
2211                                 MODBIT(term.mode, !set, MODE_ECHO);
2212                                 break;
2213                         case 20: /* LNM -- Linefeed/new line */
2214                                 MODBIT(term.mode, set, MODE_CRLF);
2215                                 break;
2216                         default:
2217                                 fprintf(stderr,
2218                                         "erresc: unknown set/reset mode %d\n",
2219                                         *args);
2220                                 break;
2221                         }
2222                 }
2223         }
2224 }
2225
2226 void
2227 csihandle(void)
2228 {
2229         char buf[40];
2230         int len;
2231
2232         switch (csiescseq.mode[0]) {
2233         default:
2234         unknown:
2235                 fprintf(stderr, "erresc: unknown csi ");
2236                 csidump();
2237                 /* die(""); */
2238                 break;
2239         case '@': /* ICH -- Insert <n> blank char */
2240                 DEFAULT(csiescseq.arg[0], 1);
2241                 tinsertblank(csiescseq.arg[0]);
2242                 break;
2243         case 'A': /* CUU -- Cursor <n> Up */
2244                 DEFAULT(csiescseq.arg[0], 1);
2245                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
2246                 break;
2247         case 'B': /* CUD -- Cursor <n> Down */
2248         case 'e': /* VPR --Cursor <n> Down */
2249                 DEFAULT(csiescseq.arg[0], 1);
2250                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
2251                 break;
2252         case 'i': /* MC -- Media Copy */
2253                 switch (csiescseq.arg[0]) {
2254                 case 0:
2255                         tdump();
2256                         break;
2257                 case 1:
2258                         tdumpline(term.c.y);
2259                         break;
2260                 case 2:
2261                         tdumpsel();
2262                         break;
2263                 case 4:
2264                         term.mode &= ~MODE_PRINT;
2265                         break;
2266                 case 5:
2267                         term.mode |= MODE_PRINT;
2268                         break;
2269                 }
2270                 break;
2271         case 'c': /* DA -- Device Attributes */
2272                 if (csiescseq.arg[0] == 0)
2273                         ttywrite(vtiden, sizeof(vtiden) - 1);
2274                 break;
2275         case 'C': /* CUF -- Cursor <n> Forward */
2276         case 'a': /* HPR -- Cursor <n> Forward */
2277                 DEFAULT(csiescseq.arg[0], 1);
2278                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
2279                 break;
2280         case 'D': /* CUB -- Cursor <n> Backward */
2281                 DEFAULT(csiescseq.arg[0], 1);
2282                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
2283                 break;
2284         case 'E': /* CNL -- Cursor <n> Down and first col */
2285                 DEFAULT(csiescseq.arg[0], 1);
2286                 tmoveto(0, term.c.y+csiescseq.arg[0]);
2287                 break;
2288         case 'F': /* CPL -- Cursor <n> Up and first col */
2289                 DEFAULT(csiescseq.arg[0], 1);
2290                 tmoveto(0, term.c.y-csiescseq.arg[0]);
2291                 break;
2292         case 'g': /* TBC -- Tabulation clear */
2293                 switch (csiescseq.arg[0]) {
2294                 case 0: /* clear current tab stop */
2295                         term.tabs[term.c.x] = 0;
2296                         break;
2297                 case 3: /* clear all the tabs */
2298                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
2299                         break;
2300                 default:
2301                         goto unknown;
2302                 }
2303                 break;
2304         case 'G': /* CHA -- Move to <col> */
2305         case '`': /* HPA */
2306                 DEFAULT(csiescseq.arg[0], 1);
2307                 tmoveto(csiescseq.arg[0]-1, term.c.y);
2308                 break;
2309         case 'H': /* CUP -- Move to <row> <col> */
2310         case 'f': /* HVP */
2311                 DEFAULT(csiescseq.arg[0], 1);
2312                 DEFAULT(csiescseq.arg[1], 1);
2313                 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
2314                 break;
2315         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
2316                 DEFAULT(csiescseq.arg[0], 1);
2317                 tputtab(csiescseq.arg[0]);
2318                 break;
2319         case 'J': /* ED -- Clear screen */
2320                 selclear(NULL);
2321                 switch (csiescseq.arg[0]) {
2322                 case 0: /* below */
2323                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
2324                         if (term.c.y < term.row-1) {
2325                                 tclearregion(0, term.c.y+1, term.col-1,
2326                                                 term.row-1);
2327                         }
2328                         break;
2329                 case 1: /* above */
2330                         if (term.c.y > 1)
2331                                 tclearregion(0, 0, term.col-1, term.c.y-1);
2332                         tclearregion(0, term.c.y, term.c.x, term.c.y);
2333                         break;
2334                 case 2: /* all */
2335                         tclearregion(0, 0, term.col-1, term.row-1);
2336                         break;
2337                 default:
2338                         goto unknown;
2339                 }
2340                 break;
2341         case 'K': /* EL -- Clear line */
2342                 switch (csiescseq.arg[0]) {
2343                 case 0: /* right */
2344                         tclearregion(term.c.x, term.c.y, term.col-1,
2345                                         term.c.y);
2346                         break;
2347                 case 1: /* left */
2348                         tclearregion(0, term.c.y, term.c.x, term.c.y);
2349                         break;
2350                 case 2: /* all */
2351                         tclearregion(0, term.c.y, term.col-1, term.c.y);
2352                         break;
2353                 }
2354                 break;
2355         case 'S': /* SU -- Scroll <n> line up */
2356                 DEFAULT(csiescseq.arg[0], 1);
2357                 tscrollup(term.top, csiescseq.arg[0]);
2358                 break;
2359         case 'T': /* SD -- Scroll <n> line down */
2360                 DEFAULT(csiescseq.arg[0], 1);
2361                 tscrolldown(term.top, csiescseq.arg[0]);
2362                 break;
2363         case 'L': /* IL -- Insert <n> blank lines */
2364                 DEFAULT(csiescseq.arg[0], 1);
2365                 tinsertblankline(csiescseq.arg[0]);
2366                 break;
2367         case 'l': /* RM -- Reset Mode */
2368                 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
2369                 break;
2370         case 'M': /* DL -- Delete <n> lines */
2371                 DEFAULT(csiescseq.arg[0], 1);
2372                 tdeleteline(csiescseq.arg[0]);
2373                 break;
2374         case 'X': /* ECH -- Erase <n> char */
2375                 DEFAULT(csiescseq.arg[0], 1);
2376                 tclearregion(term.c.x, term.c.y,
2377                                 term.c.x + csiescseq.arg[0] - 1, term.c.y);
2378                 break;
2379         case 'P': /* DCH -- Delete <n> char */
2380                 DEFAULT(csiescseq.arg[0], 1);
2381                 tdeletechar(csiescseq.arg[0]);
2382                 break;
2383         case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
2384                 DEFAULT(csiescseq.arg[0], 1);
2385                 tputtab(-csiescseq.arg[0]);
2386                 break;
2387         case 'd': /* VPA -- Move to <row> */
2388                 DEFAULT(csiescseq.arg[0], 1);
2389                 tmoveato(term.c.x, csiescseq.arg[0]-1);
2390                 break;
2391         case 'h': /* SM -- Set terminal mode */
2392                 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
2393                 break;
2394         case 'm': /* SGR -- Terminal attribute (color) */
2395                 tsetattr(csiescseq.arg, csiescseq.narg);
2396                 break;
2397         case 'n': /* DSR – Device Status Report (cursor position) */
2398                 if (csiescseq.arg[0] == 6) {
2399                         len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
2400                                         term.c.y+1, term.c.x+1);
2401                         ttywrite(buf, len);
2402                 }
2403                 break;
2404         case 'r': /* DECSTBM -- Set Scrolling Region */
2405                 if (csiescseq.priv) {
2406                         goto unknown;
2407                 } else {
2408                         DEFAULT(csiescseq.arg[0], 1);
2409                         DEFAULT(csiescseq.arg[1], term.row);
2410                         tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
2411                         tmoveato(0, 0);
2412                 }
2413                 break;
2414         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
2415                 tcursor(CURSOR_SAVE);
2416                 break;
2417         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
2418                 tcursor(CURSOR_LOAD);
2419                 break;
2420         case ' ':
2421                 switch (csiescseq.mode[1]) {
2422                         case 'q': /* DECSCUSR -- Set Cursor Style */
2423                                 DEFAULT(csiescseq.arg[0], 1);
2424                                 if (!BETWEEN(csiescseq.arg[0], 0, 6)) {
2425                                         goto unknown;
2426                                 }
2427                                 xw.cursor = csiescseq.arg[0];
2428                                 break;
2429                         default:
2430                                 goto unknown;
2431                 }
2432                 break;
2433         }
2434 }
2435
2436 void
2437 csidump(void)
2438 {
2439         int i;
2440         uint c;
2441
2442         printf("ESC[");
2443         for (i = 0; i < csiescseq.len; i++) {
2444                 c = csiescseq.buf[i] & 0xff;
2445                 if (isprint(c)) {
2446                         putchar(c);
2447                 } else if (c == '\n') {
2448                         printf("(\\n)");
2449                 } else if (c == '\r') {
2450                         printf("(\\r)");
2451                 } else if (c == 0x1b) {
2452                         printf("(\\e)");
2453                 } else {
2454                         printf("(%02x)", c);
2455                 }
2456         }
2457         putchar('\n');
2458 }
2459
2460 void
2461 csireset(void)
2462 {
2463         memset(&csiescseq, 0, sizeof(csiescseq));
2464 }
2465
2466 void
2467 strhandle(void)
2468 {
2469         char *p = NULL;
2470         int j, narg, par;
2471
2472         term.esc &= ~(ESC_STR_END|ESC_STR);
2473         strparse();
2474         par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
2475
2476         switch (strescseq.type) {
2477         case ']': /* OSC -- Operating System Command */
2478                 switch (par) {
2479                 case 0:
2480                 case 1:
2481                 case 2:
2482                         if (narg > 1)
2483                                 xsettitle(strescseq.args[1]);
2484                         return;
2485                 case 4: /* color set */
2486                         if (narg < 3)
2487                                 break;
2488                         p = strescseq.args[2];
2489                         /* FALLTHROUGH */
2490                 case 104: /* color reset, here p = NULL */
2491                         j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
2492                         if (xsetcolorname(j, p)) {
2493                                 fprintf(stderr, "erresc: invalid color %s\n", p);
2494                         } else {
2495                                 /*
2496                                  * TODO if defaultbg color is changed, borders
2497                                  * are dirty
2498                                  */
2499                                 redraw();
2500                         }
2501                         return;
2502                 }
2503                 break;
2504         case 'k': /* old title set compatibility */
2505                 xsettitle(strescseq.args[0]);
2506                 return;
2507         case 'P': /* DCS -- Device Control String */
2508         case '_': /* APC -- Application Program Command */
2509         case '^': /* PM -- Privacy Message */
2510                 return;
2511         }
2512
2513         fprintf(stderr, "erresc: unknown str ");
2514         strdump();
2515 }
2516
2517 void
2518 strparse(void)
2519 {
2520         int c;
2521         char *p = strescseq.buf;
2522
2523         strescseq.narg = 0;
2524         strescseq.buf[strescseq.len] = '\0';
2525
2526         if (*p == '\0')
2527                 return;
2528
2529         while (strescseq.narg < STR_ARG_SIZ) {
2530                 strescseq.args[strescseq.narg++] = p;
2531                 while ((c = *p) != ';' && c != '\0')
2532                         ++p;
2533                 if (c == '\0')
2534                         return;
2535                 *p++ = '\0';
2536         }
2537 }
2538
2539 void
2540 strdump(void)
2541 {
2542         int i;
2543         uint c;
2544
2545         printf("ESC%c", strescseq.type);
2546         for (i = 0; i < strescseq.len; i++) {
2547                 c = strescseq.buf[i] & 0xff;
2548                 if (c == '\0') {
2549                         return;
2550                 } else if (isprint(c)) {
2551                         putchar(c);
2552                 } else if (c == '\n') {
2553                         printf("(\\n)");
2554                 } else if (c == '\r') {
2555                         printf("(\\r)");
2556                 } else if (c == 0x1b) {
2557                         printf("(\\e)");
2558                 } else {
2559                         printf("(%02x)", c);
2560                 }
2561         }
2562         printf("ESC\\\n");
2563 }
2564
2565 void
2566 strreset(void)
2567 {
2568         memset(&strescseq, 0, sizeof(strescseq));
2569 }
2570
2571 void
2572 tprinter(char *s, size_t len)
2573 {
2574         if (iofd != -1 && xwrite(iofd, s, len) < 0) {
2575                 fprintf(stderr, "Error writing in %s:%s\n",
2576                         opt_io, strerror(errno));
2577                 close(iofd);
2578                 iofd = -1;
2579         }
2580 }
2581
2582 void
2583 toggleprinter(const Arg *arg)
2584 {
2585         term.mode ^= MODE_PRINT;
2586 }
2587
2588 void
2589 printscreen(const Arg *arg)
2590 {
2591         tdump();
2592 }
2593
2594 void
2595 printsel(const Arg *arg)
2596 {
2597         tdumpsel();
2598 }
2599
2600 void
2601 tdumpsel(void)
2602 {
2603         char *ptr;
2604
2605         if ((ptr = getsel())) {
2606                 tprinter(ptr, strlen(ptr));
2607                 free(ptr);
2608         }
2609 }
2610
2611 void
2612 tdumpline(int n)
2613 {
2614         char buf[UTF_SIZ];
2615         Glyph *bp, *end;
2616
2617         bp = &term.line[n][0];
2618         end = &bp[MIN(tlinelen(n), term.col) - 1];
2619         if (bp != end || bp->u != ' ') {
2620                 for ( ;bp <= end; ++bp)
2621                         tprinter(buf, utf8encode(bp->u, buf));
2622         }
2623         tprinter("\n", 1);
2624 }
2625
2626 void
2627 tdump(void)
2628 {
2629         int i;
2630
2631         for (i = 0; i < term.row; ++i)
2632                 tdumpline(i);
2633 }
2634
2635 void
2636 tputtab(int n)
2637 {
2638         uint x = term.c.x;
2639
2640         if (n > 0) {
2641                 while (x < term.col && n--)
2642                         for (++x; x < term.col && !term.tabs[x]; ++x)
2643                                 /* nothing */ ;
2644         } else if (n < 0) {
2645                 while (x > 0 && n++)
2646                         for (--x; x > 0 && !term.tabs[x]; --x)
2647                                 /* nothing */ ;
2648         }
2649         term.c.x = LIMIT(x, 0, term.col-1);
2650 }
2651
2652 void
2653 techo(Rune u)
2654 {
2655         if (ISCONTROL(u)) { /* control code */
2656                 if (u & 0x80) {
2657                         u &= 0x7f;
2658                         tputc('^');
2659                         tputc('[');
2660                 } else if (u != '\n' && u != '\r' && u != '\t') {
2661                         u ^= 0x40;
2662                         tputc('^');
2663                 }
2664         }
2665         tputc(u);
2666 }
2667
2668 void
2669 tdeftran(char ascii)
2670 {
2671         static char cs[] = "0B";
2672         static int vcs[] = {CS_GRAPHIC0, CS_USA};
2673         char *p;
2674
2675         if ((p = strchr(cs, ascii)) == NULL) {
2676                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2677         } else {
2678                 term.trantbl[term.icharset] = vcs[p - cs];
2679         }
2680 }
2681
2682 void
2683 tdectest(char c)
2684 {
2685         int x, y;
2686
2687         if (c == '8') { /* DEC screen alignment test. */
2688                 for (x = 0; x < term.col; ++x) {
2689                         for (y = 0; y < term.row; ++y)
2690                                 tsetchar('E', &term.c.attr, x, y);
2691                 }
2692         }
2693 }
2694
2695 void
2696 tstrsequence(uchar c)
2697 {
2698         switch (c) {
2699         case 0x90:   /* DCS -- Device Control String */
2700                 c = 'P';
2701                 break;
2702         case 0x9f:   /* APC -- Application Program Command */
2703                 c = '_';
2704                 break;
2705         case 0x9e:   /* PM -- Privacy Message */
2706                 c = '^';
2707                 break;
2708         case 0x9d:   /* OSC -- Operating System Command */
2709                 c = ']';
2710                 break;
2711         }
2712         strreset();
2713         strescseq.type = c;
2714         term.esc |= ESC_STR;
2715 }
2716
2717 void
2718 tcontrolcode(uchar ascii)
2719 {
2720         switch (ascii) {
2721         case '\t':   /* HT */
2722                 tputtab(1);
2723                 return;
2724         case '\b':   /* BS */
2725                 tmoveto(term.c.x-1, term.c.y);
2726                 return;
2727         case '\r':   /* CR */
2728                 tmoveto(0, term.c.y);
2729                 return;
2730         case '\f':   /* LF */
2731         case '\v':   /* VT */
2732         case '\n':   /* LF */
2733                 /* go to first col if the mode is set */
2734                 tnewline(IS_SET(MODE_CRLF));
2735                 return;
2736         case '\a':   /* BEL */
2737                 if (term.esc & ESC_STR_END) {
2738                         /* backwards compatibility to xterm */
2739                         strhandle();
2740                 } else {
2741                         if (!(xw.state & WIN_FOCUSED))
2742                                 xseturgency(1);
2743                         if (bellvolume)
2744                                 XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL);
2745                 }
2746                 break;
2747         case '\033': /* ESC */
2748                 csireset();
2749                 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2750                 term.esc |= ESC_START;
2751                 return;
2752         case '\016': /* SO (LS1 -- Locking shift 1) */
2753         case '\017': /* SI (LS0 -- Locking shift 0) */
2754                 term.charset = 1 - (ascii - '\016');
2755                 return;
2756         case '\032': /* SUB */
2757                 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2758         case '\030': /* CAN */
2759                 csireset();
2760                 break;
2761         case '\005': /* ENQ (IGNORED) */
2762         case '\000': /* NUL (IGNORED) */
2763         case '\021': /* XON (IGNORED) */
2764         case '\023': /* XOFF (IGNORED) */
2765         case 0177:   /* DEL (IGNORED) */
2766                 return;
2767         case 0x84:   /* TODO: IND */
2768                 break;
2769         case 0x85:   /* NEL -- Next line */
2770                 tnewline(1); /* always go to first col */
2771                 break;
2772         case 0x88:   /* HTS -- Horizontal tab stop */
2773                 term.tabs[term.c.x] = 1;
2774                 break;
2775         case 0x8d:   /* TODO: RI */
2776         case 0x8e:   /* TODO: SS2 */
2777         case 0x8f:   /* TODO: SS3 */
2778         case 0x98:   /* TODO: SOS */
2779                 break;
2780         case 0x9a:   /* DECID -- Identify Terminal */
2781                 ttywrite(vtiden, sizeof(vtiden) - 1);
2782                 break;
2783         case 0x9b:   /* TODO: CSI */
2784         case 0x9c:   /* TODO: ST */
2785                 break;
2786         case 0x90:   /* DCS -- Device Control String */
2787         case 0x9f:   /* APC -- Application Program Command */
2788         case 0x9e:   /* PM -- Privacy Message */
2789         case 0x9d:   /* OSC -- Operating System Command */
2790                 tstrsequence(ascii);
2791                 return;
2792         }
2793         /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2794         term.esc &= ~(ESC_STR_END|ESC_STR);
2795 }
2796
2797 /*
2798  * returns 1 when the sequence is finished and it hasn't to read
2799  * more characters for this sequence, otherwise 0
2800  */
2801 int
2802 eschandle(uchar ascii)
2803 {
2804         switch (ascii) {
2805         case '[':
2806                 term.esc |= ESC_CSI;
2807                 return 0;
2808         case '#':
2809                 term.esc |= ESC_TEST;
2810                 return 0;
2811         case 'P': /* DCS -- Device Control String */
2812         case '_': /* APC -- Application Program Command */
2813         case '^': /* PM -- Privacy Message */
2814         case ']': /* OSC -- Operating System Command */
2815         case 'k': /* old title set compatibility */
2816                 tstrsequence(ascii);
2817                 return 0;
2818         case 'n': /* LS2 -- Locking shift 2 */
2819         case 'o': /* LS3 -- Locking shift 3 */
2820                 term.charset = 2 + (ascii - 'n');
2821                 break;
2822         case '(': /* GZD4 -- set primary charset G0 */
2823         case ')': /* G1D4 -- set secondary charset G1 */
2824         case '*': /* G2D4 -- set tertiary charset G2 */
2825         case '+': /* G3D4 -- set quaternary charset G3 */
2826                 term.icharset = ascii - '(';
2827                 term.esc |= ESC_ALTCHARSET;
2828                 return 0;
2829         case 'D': /* IND -- Linefeed */
2830                 if (term.c.y == term.bot) {
2831                         tscrollup(term.top, 1);
2832                 } else {
2833                         tmoveto(term.c.x, term.c.y+1);
2834                 }
2835                 break;
2836         case 'E': /* NEL -- Next line */
2837                 tnewline(1); /* always go to first col */
2838                 break;
2839         case 'H': /* HTS -- Horizontal tab stop */
2840                 term.tabs[term.c.x] = 1;
2841                 break;
2842         case 'M': /* RI -- Reverse index */
2843                 if (term.c.y == term.top) {
2844                         tscrolldown(term.top, 1);
2845                 } else {
2846                         tmoveto(term.c.x, term.c.y-1);
2847                 }
2848                 break;
2849         case 'Z': /* DECID -- Identify Terminal */
2850                 ttywrite(vtiden, sizeof(vtiden) - 1);
2851                 break;
2852         case 'c': /* RIS -- Reset to inital state */
2853                 treset();
2854                 xresettitle();
2855                 xloadcols();
2856                 break;
2857         case '=': /* DECPAM -- Application keypad */
2858                 term.mode |= MODE_APPKEYPAD;
2859                 break;
2860         case '>': /* DECPNM -- Normal keypad */
2861                 term.mode &= ~MODE_APPKEYPAD;
2862                 break;
2863         case '7': /* DECSC -- Save Cursor */
2864                 tcursor(CURSOR_SAVE);
2865                 break;
2866         case '8': /* DECRC -- Restore Cursor */
2867                 tcursor(CURSOR_LOAD);
2868                 break;
2869         case '\\': /* ST -- String Terminator */
2870                 if (term.esc & ESC_STR_END)
2871                         strhandle();
2872                 break;
2873         default:
2874                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2875                         (uchar) ascii, isprint(ascii)? ascii:'.');
2876                 break;
2877         }
2878         return 1;
2879 }
2880
2881 void
2882 tputc(Rune u)
2883 {
2884         char c[UTF_SIZ];
2885         int control;
2886         int width, len;
2887         Glyph *gp;
2888
2889         len = utf8encode(u, c);
2890         if ((width = wcwidth(u)) == -1) {
2891                 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2892                 width = 1;
2893         }
2894
2895         if (IS_SET(MODE_PRINT))
2896                 tprinter(c, len);
2897         control = ISCONTROL(u);
2898
2899         /*
2900          * STR sequence must be checked before anything else
2901          * because it uses all following characters until it
2902          * receives a ESC, a SUB, a ST or any other C1 control
2903          * character.
2904          */
2905         if (term.esc & ESC_STR) {
2906                 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2907                    ISCONTROLC1(u)) {
2908                         term.esc &= ~(ESC_START|ESC_STR);
2909                         term.esc |= ESC_STR_END;
2910                 } else if (strescseq.len + len < sizeof(strescseq.buf) - 1) {
2911                         memmove(&strescseq.buf[strescseq.len], c, len);
2912                         strescseq.len += len;
2913                         return;
2914                 } else {
2915                 /*
2916                  * Here is a bug in terminals. If the user never sends
2917                  * some code to stop the str or esc command, then st
2918                  * will stop responding. But this is better than
2919                  * silently failing with unknown characters. At least
2920                  * then users will report back.
2921                  *
2922                  * In the case users ever get fixed, here is the code:
2923                  */
2924                 /*
2925                  * term.esc = 0;
2926                  * strhandle();
2927                  */
2928                         return;
2929                 }
2930         }
2931
2932         /*
2933          * Actions of control codes must be performed as soon they arrive
2934          * because they can be embedded inside a control sequence, and
2935          * they must not cause conflicts with sequences.
2936          */
2937         if (control) {
2938                 tcontrolcode(u);
2939                 /*
2940                  * control codes are not shown ever
2941                  */
2942                 return;
2943         } else if (term.esc & ESC_START) {
2944                 if (term.esc & ESC_CSI) {
2945                         csiescseq.buf[csiescseq.len++] = u;
2946                         if (BETWEEN(u, 0x40, 0x7E)
2947                                         || csiescseq.len >= \
2948                                         sizeof(csiescseq.buf)-1) {
2949                                 term.esc = 0;
2950                                 csiparse();
2951                                 csihandle();
2952                         }
2953                         return;
2954                 } else if (term.esc & ESC_ALTCHARSET) {
2955                         tdeftran(u);
2956                 } else if (term.esc & ESC_TEST) {
2957                         tdectest(u);
2958                 } else {
2959                         if (!eschandle(u))
2960                                 return;
2961                         /* sequence already finished */
2962                 }
2963                 term.esc = 0;
2964                 /*
2965                  * All characters which form part of a sequence are not
2966                  * printed
2967                  */
2968                 return;
2969         }
2970         if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2971                 selclear(NULL);
2972
2973         gp = &term.line[term.c.y][term.c.x];
2974         if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2975                 gp->mode |= ATTR_WRAP;
2976                 tnewline(1);
2977                 gp = &term.line[term.c.y][term.c.x];
2978         }
2979
2980         if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2981                 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2982
2983         if (term.c.x+width > term.col) {
2984                 tnewline(1);
2985                 gp = &term.line[term.c.y][term.c.x];
2986         }
2987
2988         tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2989
2990         if (width == 2) {
2991                 gp->mode |= ATTR_WIDE;
2992                 if (term.c.x+1 < term.col) {
2993                         gp[1].u = '\0';
2994                         gp[1].mode = ATTR_WDUMMY;
2995                 }
2996         }
2997         if (term.c.x+width < term.col) {
2998                 tmoveto(term.c.x+width, term.c.y);
2999         } else {
3000                 term.c.state |= CURSOR_WRAPNEXT;
3001         }
3002 }
3003
3004 void
3005 tresize(int col, int row)
3006 {
3007         int i;
3008         int minrow = MIN(row, term.row);
3009         int mincol = MIN(col, term.col);
3010         int *bp;
3011         TCursor c;
3012
3013         if (col < 1 || row < 1) {
3014                 fprintf(stderr,
3015                         "tresize: error resizing to %dx%d\n", col, row);
3016                 return;
3017         }
3018
3019         /*
3020          * slide screen to keep cursor where we expect it -
3021          * tscrollup would work here, but we can optimize to
3022          * memmove because we're freeing the earlier lines
3023          */
3024         for (i = 0; i <= term.c.y - row; i++) {
3025                 free(term.line[i]);
3026                 free(term.alt[i]);
3027         }
3028         /* ensure that both src and dst are not NULL */
3029         if (i > 0) {
3030                 memmove(term.line, term.line + i, row * sizeof(Line));
3031                 memmove(term.alt, term.alt + i, row * sizeof(Line));
3032         }
3033         for (i += row; i < term.row; i++) {
3034                 free(term.line[i]);
3035                 free(term.alt[i]);
3036         }
3037
3038         /* resize to new width */
3039         term.specbuf = xrealloc(term.specbuf, col * sizeof(XftGlyphFontSpec));
3040
3041         /* resize to new height */
3042         term.line = xrealloc(term.line, row * sizeof(Line));
3043         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
3044         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
3045         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
3046
3047         /* resize each row to new width, zero-pad if needed */
3048         for (i = 0; i < minrow; i++) {
3049                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
3050                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
3051         }
3052
3053         /* allocate any new rows */
3054         for (/* i == minrow */; i < row; i++) {
3055                 term.line[i] = xmalloc(col * sizeof(Glyph));
3056                 term.alt[i] = xmalloc(col * sizeof(Glyph));
3057         }
3058         if (col > term.col) {
3059                 bp = term.tabs + term.col;
3060
3061                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
3062                 while (--bp > term.tabs && !*bp)
3063                         /* nothing */ ;
3064                 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
3065                         *bp = 1;
3066         }
3067         /* update terminal size */
3068         term.col = col;
3069         term.row = row;
3070         /* reset scrolling region */
3071         tsetscroll(0, row-1);
3072         /* make use of the LIMIT in tmoveto */
3073         tmoveto(term.c.x, term.c.y);
3074         /* Clearing both screens (it makes dirty all lines) */
3075         c = term.c;
3076         for (i = 0; i < 2; i++) {
3077                 if (mincol < col && 0 < minrow) {
3078                         tclearregion(mincol, 0, col - 1, minrow - 1);
3079                 }
3080                 if (0 < col && minrow < row) {
3081                         tclearregion(0, minrow, col - 1, row - 1);
3082                 }
3083                 tswapscreen();
3084                 tcursor(CURSOR_LOAD);
3085         }
3086         term.c = c;
3087 }
3088
3089 void
3090 xresize(int col, int row)
3091 {
3092         xw.tw = MAX(1, col * xw.cw);
3093         xw.th = MAX(1, row * xw.ch);
3094
3095         XFreePixmap(xw.dpy, xw.buf);
3096         xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h,
3097                         DefaultDepth(xw.dpy, xw.scr));
3098         XftDrawChange(xw.draw, xw.buf);
3099         xclear(0, 0, xw.w, xw.h);
3100 }
3101
3102 ushort
3103 sixd_to_16bit(int x)
3104 {
3105         return x == 0 ? 0 : 0x3737 + 0x2828 * x;
3106 }
3107
3108 int
3109 xloadcolor(int i, const char *name, Color *ncolor)
3110 {
3111         XRenderColor color = { .alpha = 0xffff };
3112
3113         if (!name) {
3114                 if (BETWEEN(i, 16, 255)) { /* 256 color */
3115                         if (i < 6*6*6+16) { /* same colors as xterm */
3116                                 color.red   = sixd_to_16bit( ((i-16)/36)%6 );
3117                                 color.green = sixd_to_16bit( ((i-16)/6) %6 );
3118                                 color.blue  = sixd_to_16bit( ((i-16)/1) %6 );
3119                         } else { /* greyscale */
3120                                 color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16));
3121                                 color.green = color.blue = color.red;
3122                         }
3123                         return XftColorAllocValue(xw.dpy, xw.vis,
3124                                                   xw.cmap, &color, ncolor);
3125                 } else
3126                         name = colorname[i];
3127         }
3128         return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor);
3129 }
3130
3131 void
3132 xloadcols(void)
3133 {
3134         int i;
3135         static int loaded;
3136         Color *cp;
3137
3138         if (loaded) {
3139                 for (cp = dc.col; cp < &dc.col[LEN(dc.col)]; ++cp)
3140                         XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
3141         }
3142
3143         for (i = 0; i < LEN(dc.col); i++)
3144                 if (!xloadcolor(i, NULL, &dc.col[i])) {
3145                         if (colorname[i])
3146                                 die("Could not allocate color '%s'\n", colorname[i]);
3147                         else
3148                                 die("Could not allocate color %d\n", i);
3149                 }
3150         loaded = 1;
3151 }
3152
3153 int
3154 xsetcolorname(int x, const char *name)
3155 {
3156         Color ncolor;
3157
3158         if (!BETWEEN(x, 0, LEN(dc.col)))
3159                 return 1;
3160
3161
3162         if (!xloadcolor(x, name, &ncolor))
3163                 return 1;
3164
3165         XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);
3166         dc.col[x] = ncolor;
3167         return 0;
3168 }
3169
3170 void
3171 xtermclear(int col1, int row1, int col2, int row2)
3172 {
3173         XftDrawRect(xw.draw,
3174                         &dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg],
3175                         borderpx + col1 * xw.cw,
3176                         borderpx + row1 * xw.ch,
3177                         (col2-col1+1) * xw.cw,
3178                         (row2-row1+1) * xw.ch);
3179 }
3180
3181 /*
3182  * Absolute coordinates.
3183  */
3184 void
3185 xclear(int x1, int y1, int x2, int y2)
3186 {
3187         XftDrawRect(xw.draw,
3188                         &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg],
3189                         x1, y1, x2-x1, y2-y1);
3190 }
3191
3192 void
3193 xhints(void)
3194 {
3195         XClassHint class = {opt_class ? opt_class : termname, termname};
3196         XWMHints wm = {.flags = InputHint, .input = 1};
3197         XSizeHints *sizeh = NULL;
3198
3199         sizeh = XAllocSizeHints();
3200
3201         sizeh->flags = PSize | PResizeInc | PBaseSize;
3202         sizeh->height = xw.h;
3203         sizeh->width = xw.w;
3204         sizeh->height_inc = xw.ch;
3205         sizeh->width_inc = xw.cw;
3206         sizeh->base_height = 2 * borderpx;
3207         sizeh->base_width = 2 * borderpx;
3208         if (xw.isfixed) {
3209                 sizeh->flags |= PMaxSize | PMinSize;
3210                 sizeh->min_width = sizeh->max_width = xw.w;
3211                 sizeh->min_height = sizeh->max_height = xw.h;
3212         }
3213         if (xw.gm & (XValue|YValue)) {
3214                 sizeh->flags |= USPosition | PWinGravity;
3215                 sizeh->x = xw.l;
3216                 sizeh->y = xw.t;
3217                 sizeh->win_gravity = xgeommasktogravity(xw.gm);
3218         }
3219
3220         XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm,
3221                         &class);
3222         XFree(sizeh);
3223 }
3224
3225 int
3226 xgeommasktogravity(int mask)
3227 {
3228         switch (mask & (XNegative|YNegative)) {
3229         case 0:
3230                 return NorthWestGravity;
3231         case XNegative:
3232                 return NorthEastGravity;
3233         case YNegative:
3234                 return SouthWestGravity;
3235         }
3236         return SouthEastGravity;
3237 }
3238
3239 int
3240 xloadfont(Font *f, FcPattern *pattern)
3241 {
3242         FcPattern *match;
3243         FcResult result;
3244
3245         match = FcFontMatch(NULL, pattern, &result);
3246         if (!match)
3247                 return 1;
3248
3249         if (!(f->match = XftFontOpenPattern(xw.dpy, match))) {
3250                 FcPatternDestroy(match);
3251                 return 1;
3252         }
3253
3254         f->set = NULL;
3255         f->pattern = FcPatternDuplicate(pattern);
3256
3257         f->ascent = f->match->ascent;
3258         f->descent = f->match->descent;
3259         f->lbearing = 0;
3260         f->rbearing = f->match->max_advance_width;
3261
3262         f->height = f->ascent + f->descent;
3263         f->width = f->lbearing + f->rbearing;
3264
3265         return 0;
3266 }
3267
3268 void
3269 xloadfonts(char *fontstr, double fontsize)
3270 {
3271         FcPattern *pattern;
3272         double fontval;
3273         float ceilf(float);
3274
3275         if (fontstr[0] == '-') {
3276                 pattern = XftXlfdParse(fontstr, False, False);
3277         } else {
3278                 pattern = FcNameParse((FcChar8 *)fontstr);
3279         }
3280
3281         if (!pattern)
3282                 die("st: can't open font %s\n", fontstr);
3283
3284         if (fontsize > 1) {
3285                 FcPatternDel(pattern, FC_PIXEL_SIZE);
3286                 FcPatternDel(pattern, FC_SIZE);
3287                 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize);
3288                 usedfontsize = fontsize;
3289         } else {
3290                 if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
3291                                 FcResultMatch) {
3292                         usedfontsize = fontval;
3293                 } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) ==
3294                                 FcResultMatch) {
3295                         usedfontsize = -1;
3296                 } else {
3297                         /*
3298                          * Default font size is 12, if none given. This is to
3299                          * have a known usedfontsize value.
3300                          */
3301                         FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);
3302                         usedfontsize = 12;
3303                 }
3304                 defaultfontsize = usedfontsize;
3305         }
3306
3307         FcConfigSubstitute(0, pattern, FcMatchPattern);
3308         FcDefaultSubstitute(pattern);
3309
3310         if (xloadfont(&dc.font, pattern))
3311                 die("st: can't open font %s\n", fontstr);
3312
3313         if (usedfontsize < 0) {
3314                 FcPatternGetDouble(dc.font.match->pattern,
3315                                    FC_PIXEL_SIZE, 0, &fontval);
3316                 usedfontsize = fontval;
3317                 if (fontsize == 0)
3318                         defaultfontsize = fontval;
3319         }
3320
3321         /* Setting character width and height. */
3322         xw.cw = ceilf(dc.font.width * cwscale);
3323         xw.ch = ceilf(dc.font.height * chscale);
3324
3325         FcPatternDel(pattern, FC_SLANT);
3326         FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
3327         if (xloadfont(&dc.ifont, pattern))
3328                 die("st: can't open font %s\n", fontstr);
3329
3330         FcPatternDel(pattern, FC_WEIGHT);
3331         FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
3332         if (xloadfont(&dc.ibfont, pattern))
3333                 die("st: can't open font %s\n", fontstr);
3334
3335         FcPatternDel(pattern, FC_SLANT);
3336         FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
3337         if (xloadfont(&dc.bfont, pattern))
3338                 die("st: can't open font %s\n", fontstr);
3339
3340         FcPatternDestroy(pattern);
3341 }
3342
3343 void
3344 xunloadfont(Font *f)
3345 {
3346         XftFontClose(xw.dpy, f->match);
3347         FcPatternDestroy(f->pattern);
3348         if (f->set)
3349                 FcFontSetDestroy(f->set);
3350 }
3351
3352 void
3353 xunloadfonts(void)
3354 {
3355         /* Free the loaded fonts in the font cache.  */
3356         while (frclen > 0)
3357                 XftFontClose(xw.dpy, frc[--frclen].font);
3358
3359         xunloadfont(&dc.font);
3360         xunloadfont(&dc.bfont);
3361         xunloadfont(&dc.ifont);
3362         xunloadfont(&dc.ibfont);
3363 }
3364
3365 void
3366 xzoom(const Arg *arg)
3367 {
3368         Arg larg;
3369
3370         larg.f = usedfontsize + arg->f;
3371         xzoomabs(&larg);
3372 }
3373
3374 void
3375 xzoomabs(const Arg *arg)
3376 {
3377         xunloadfonts();
3378         xloadfonts(usedfont, arg->f);
3379         cresize(0, 0);
3380         redraw();
3381         xhints();
3382 }
3383
3384 void
3385 xzoomreset(const Arg *arg)
3386 {
3387         Arg larg;
3388
3389         if (defaultfontsize > 0) {
3390                 larg.f = defaultfontsize;
3391                 xzoomabs(&larg);
3392         }
3393 }
3394
3395 void
3396 xinit(void)
3397 {
3398         XGCValues gcvalues;
3399         Cursor cursor;
3400         Window parent;
3401         pid_t thispid = getpid();
3402
3403         if (!(xw.dpy = XOpenDisplay(NULL)))
3404                 die("Can't open display\n");
3405         xw.scr = XDefaultScreen(xw.dpy);
3406         xw.vis = XDefaultVisual(xw.dpy, xw.scr);
3407
3408         /* font */
3409         if (!FcInit())
3410                 die("Could not init fontconfig.\n");
3411
3412         usedfont = (opt_font == NULL)? font : opt_font;
3413         xloadfonts(usedfont, 0);
3414
3415         /* colors */
3416         xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
3417         xloadcols();
3418
3419         /* adjust fixed window geometry */
3420         xw.w = 2 * borderpx + term.col * xw.cw;
3421         xw.h = 2 * borderpx + term.row * xw.ch;
3422         if (xw.gm & XNegative)
3423                 xw.l += DisplayWidth(xw.dpy, xw.scr) - xw.w - 2;
3424         if (xw.gm & YNegative)
3425                 xw.t += DisplayWidth(xw.dpy, xw.scr) - xw.h - 2;
3426
3427         /* Events */
3428         xw.attrs.background_pixel = dc.col[defaultbg].pixel;
3429         xw.attrs.border_pixel = dc.col[defaultbg].pixel;
3430         xw.attrs.bit_gravity = NorthWestGravity;
3431         xw.attrs.event_mask = FocusChangeMask | KeyPressMask
3432                 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
3433                 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
3434         xw.attrs.colormap = xw.cmap;
3435
3436         if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0))))
3437                 parent = XRootWindow(xw.dpy, xw.scr);
3438         xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t,
3439                         xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
3440                         xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity
3441                         | CWEventMask | CWColormap, &xw.attrs);
3442
3443         memset(&gcvalues, 0, sizeof(gcvalues));
3444         gcvalues.graphics_exposures = False;
3445         dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures,
3446                         &gcvalues);
3447         xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h,
3448                         DefaultDepth(xw.dpy, xw.scr));
3449         XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
3450         XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, xw.w, xw.h);
3451
3452         /* Xft rendering context */
3453         xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
3454
3455         /* input methods */
3456         if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) {
3457                 XSetLocaleModifiers("@im=local");
3458                 if ((xw.xim =  XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) {
3459                         XSetLocaleModifiers("@im=");
3460                         if ((xw.xim = XOpenIM(xw.dpy,
3461                                         NULL, NULL, NULL)) == NULL) {
3462                                 die("XOpenIM failed. Could not open input"
3463                                         " device.\n");
3464                         }
3465                 }
3466         }
3467         xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing
3468                                            | XIMStatusNothing, XNClientWindow, xw.win,
3469                                            XNFocusWindow, xw.win, NULL);
3470         if (xw.xic == NULL)
3471                 die("XCreateIC failed. Could not obtain input method.\n");
3472
3473         /* white cursor, black outline */
3474         cursor = XCreateFontCursor(xw.dpy, XC_xterm);
3475         XDefineCursor(xw.dpy, xw.win, cursor);
3476         XRecolorCursor(xw.dpy, cursor,
3477                 &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff},
3478                 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
3479
3480         xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
3481         xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
3482         xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
3483         XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
3484
3485         xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False);
3486         XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32,
3487                         PropModeReplace, (uchar *)&thispid, 1);
3488
3489         xresettitle();
3490         XMapWindow(xw.dpy, xw.win);
3491         xhints();
3492         XSync(xw.dpy, False);
3493 }
3494
3495 int
3496 xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
3497 {
3498         float winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch, xp, yp;
3499         ushort mode, prevmode = USHRT_MAX;
3500         Font *font = &dc.font;
3501         int frcflags = FRC_NORMAL;
3502         float runewidth = xw.cw;
3503         Rune rune;
3504         FT_UInt glyphidx;
3505         FcResult fcres;
3506         FcPattern *fcpattern, *fontpattern;
3507         FcFontSet *fcsets[] = { NULL };
3508         FcCharSet *fccharset;
3509         int i, f, numspecs = 0;
3510
3511         for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
3512                 /* Fetch rune and mode for current glyph. */
3513                 rune = glyphs[i].u;
3514                 mode = glyphs[i].mode;
3515
3516                 /* Skip dummy wide-character spacing. */
3517                 if (mode == ATTR_WDUMMY)
3518                         continue;
3519
3520                 /* Determine font for glyph if different from previous glyph. */
3521                 if (prevmode != mode) {
3522                         prevmode = mode;
3523                         font = &dc.font;
3524                         frcflags = FRC_NORMAL;
3525                         runewidth = xw.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
3526                         if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
3527                                 font = &dc.ibfont;
3528                                 frcflags = FRC_ITALICBOLD;
3529                         } else if (mode & ATTR_ITALIC) {
3530                                 font = &dc.ifont;
3531                                 frcflags = FRC_ITALIC;
3532                         } else if (mode & ATTR_BOLD) {
3533                                 font = &dc.bfont;
3534                                 frcflags = FRC_BOLD;
3535                         }
3536                         yp = winy + font->ascent;
3537                 }
3538
3539                 /* Lookup character index with default font. */
3540                 glyphidx = XftCharIndex(xw.dpy, font->match, rune);
3541                 if (glyphidx) {
3542                         specs[numspecs].font = font->match;
3543                         specs[numspecs].glyph = glyphidx;
3544                         specs[numspecs].x = (short)xp;
3545                         specs[numspecs].y = (short)yp;
3546                         xp += runewidth;
3547                         numspecs++;
3548                         continue;
3549                 }
3550
3551                 /* Fallback on font cache, search the font cache for match. */
3552                 for (f = 0; f < frclen; f++) {
3553                         glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
3554                         /* Everything correct. */
3555                         if (glyphidx && frc[f].flags == frcflags)
3556                                 break;
3557                         /* We got a default font for a not found glyph. */
3558                         if (!glyphidx && frc[f].flags == frcflags
3559                                         && frc[f].unicodep == rune) {
3560                                 break;
3561                         }
3562                 }
3563
3564                 /* Nothing was found. Use fontconfig to find matching font. */
3565                 if (f >= frclen) {
3566                         if (!font->set)
3567                                 font->set = FcFontSort(0, font->pattern,
3568                                                        1, 0, &fcres);
3569                         fcsets[0] = font->set;
3570
3571                         /*
3572                          * Nothing was found in the cache. Now use
3573                          * some dozen of Fontconfig calls to get the
3574                          * font for one single character.
3575                          *
3576                          * Xft and fontconfig are design failures.
3577                          */
3578                         fcpattern = FcPatternDuplicate(font->pattern);
3579                         fccharset = FcCharSetCreate();
3580
3581                         FcCharSetAddChar(fccharset, rune);
3582                         FcPatternAddCharSet(fcpattern, FC_CHARSET,
3583                                         fccharset);
3584                         FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
3585
3586                         FcConfigSubstitute(0, fcpattern,
3587                                         FcMatchPattern);
3588                         FcDefaultSubstitute(fcpattern);
3589
3590                         fontpattern = FcFontSetMatch(0, fcsets, 1,
3591                                         fcpattern, &fcres);
3592
3593                         /*
3594                          * Overwrite or create the new cache entry.
3595                          */
3596                         if (frclen >= LEN(frc)) {
3597                                 frclen = LEN(frc) - 1;
3598                                 XftFontClose(xw.dpy, frc[frclen].font);
3599                                 frc[frclen].unicodep = 0;
3600                         }
3601
3602                         frc[frclen].font = XftFontOpenPattern(xw.dpy,
3603                                         fontpattern);
3604                         frc[frclen].flags = frcflags;
3605                         frc[frclen].unicodep = rune;
3606
3607                         glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
3608
3609                         f = frclen;
3610                         frclen++;
3611
3612                         FcPatternDestroy(fcpattern);
3613                         FcCharSetDestroy(fccharset);
3614                 }
3615
3616                 specs[numspecs].font = frc[f].font;
3617                 specs[numspecs].glyph = glyphidx;
3618                 specs[numspecs].x = (short)xp;
3619                 specs[numspecs].y = (short)(winy + frc[f].font->ascent);
3620                 xp += runewidth;
3621                 numspecs++;
3622         }
3623
3624         return numspecs;
3625 }
3626
3627 void
3628 xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
3629 {
3630         int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
3631         int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch,
3632             width = charlen * xw.cw;
3633         Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
3634         XRenderColor colfg, colbg;
3635         XRectangle r;
3636
3637         /* Determine foreground and background colors based on mode. */
3638         if (base.fg == defaultfg) {
3639                 if (base.mode & ATTR_ITALIC)
3640                         base.fg = defaultitalic;
3641                 else if ((base.mode & ATTR_ITALIC) && (base.mode & ATTR_BOLD))
3642                         base.fg = defaultitalic;
3643                 else if (base.mode & ATTR_UNDERLINE)
3644                         base.fg = defaultunderline;
3645         }
3646
3647         if (IS_TRUECOL(base.fg)) {
3648                 colfg.alpha = 0xffff;
3649                 colfg.red = TRUERED(base.fg);
3650                 colfg.green = TRUEGREEN(base.fg);
3651                 colfg.blue = TRUEBLUE(base.fg);
3652                 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg);
3653                 fg = &truefg;
3654         } else {
3655                 fg = &dc.col[base.fg];
3656         }
3657
3658         if (IS_TRUECOL(base.bg)) {
3659                 colbg.alpha = 0xffff;
3660                 colbg.green = TRUEGREEN(base.bg);
3661                 colbg.red = TRUERED(base.bg);
3662                 colbg.blue = TRUEBLUE(base.bg);
3663                 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg);
3664                 bg = &truebg;
3665         } else {
3666                 bg = &dc.col[base.bg];
3667         }
3668
3669         /* Change basic system colors [0-7] to bright system colors [8-15] */
3670         if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
3671                 fg = &dc.col[base.fg + 8];
3672
3673         if (IS_SET(MODE_REVERSE)) {
3674                 if (fg == &dc.col[defaultfg]) {
3675                         fg = &dc.col[defaultbg];
3676                 } else {
3677                         colfg.red = ~fg->color.red;
3678                         colfg.green = ~fg->color.green;
3679                         colfg.blue = ~fg->color.blue;
3680                         colfg.alpha = fg->color.alpha;
3681                         XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg,
3682                                         &revfg);
3683                         fg = &revfg;
3684                 }
3685
3686                 if (bg == &dc.col[defaultbg]) {
3687                         bg = &dc.col[defaultfg];
3688                 } else {
3689                         colbg.red = ~bg->color.red;
3690                         colbg.green = ~bg->color.green;
3691                         colbg.blue = ~bg->color.blue;
3692                         colbg.alpha = bg->color.alpha;
3693                         XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg,
3694                                         &revbg);
3695                         bg = &revbg;
3696                 }
3697         }
3698
3699         if (base.mode & ATTR_REVERSE) {
3700                 temp = fg;
3701                 fg = bg;
3702                 bg = temp;
3703         }
3704
3705         if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) {
3706                 colfg.red = fg->color.red / 2;
3707                 colfg.green = fg->color.green / 2;
3708                 colfg.blue = fg->color.blue / 2;
3709                 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg);
3710                 fg = &revfg;
3711         }
3712
3713         if (base.mode & ATTR_BLINK && term.mode & MODE_BLINK)
3714                 fg = bg;
3715
3716         if (base.mode & ATTR_INVISIBLE)
3717                 fg = bg;
3718
3719         /* Intelligent cleaning up of the borders. */
3720         if (x == 0) {
3721                 xclear(0, (y == 0)? 0 : winy, borderpx,
3722                         winy + xw.ch + ((y >= term.row-1)? xw.h : 0));
3723         }
3724         if (x + charlen >= term.col) {
3725                 xclear(winx + width, (y == 0)? 0 : winy, xw.w,
3726                         ((y >= term.row-1)? xw.h : (winy + xw.ch)));
3727         }
3728         if (y == 0)
3729                 xclear(winx, 0, winx + width, borderpx);
3730         if (y == term.row-1)
3731                 xclear(winx, winy + xw.ch, winx + width, xw.h);
3732
3733         /* Clean up the region we want to draw to. */
3734         XftDrawRect(xw.draw, bg, winx, winy, width, xw.ch);
3735
3736         /* Set the clip region because Xft is sometimes dirty. */
3737         r.x = 0;
3738         r.y = 0;
3739         r.height = xw.ch;
3740         r.width = width;
3741         XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
3742
3743         /* Render the glyphs. */
3744         XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
3745
3746         /* Render underline and strikethrough. */
3747         if (base.mode & ATTR_UNDERLINE) {
3748                 XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,
3749                                 width, 1);
3750         }
3751
3752         if (base.mode & ATTR_STRUCK) {
3753                 XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3,
3754                                 width, 1);
3755         }
3756
3757         /* Reset clip to none. */
3758         XftDrawSetClip(xw.draw, 0);
3759 }
3760
3761 void
3762 xdrawglyph(Glyph g, int x, int y)
3763 {
3764         int numspecs;
3765         XftGlyphFontSpec spec;
3766         numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
3767         xdrawglyphfontspecs(&spec, g, numspecs, x, y);
3768 }
3769
3770 void
3771 xdrawcursor(void)
3772 {
3773         static int oldx = 0, oldy = 0;
3774         int curx;
3775         Glyph g = {' ', ATTR_NULL, defaultbg, defaultcs};
3776
3777         LIMIT(oldx, 0, term.col-1);
3778         LIMIT(oldy, 0, term.row-1);
3779
3780         curx = term.c.x;
3781
3782         /* adjust position if in dummy */
3783         if (term.line[oldy][oldx].mode & ATTR_WDUMMY)
3784                 oldx--;
3785         if (term.line[term.c.y][curx].mode & ATTR_WDUMMY)
3786                 curx--;
3787
3788         g.u = term.line[term.c.y][term.c.x].u;
3789
3790         /* remove the old cursor */
3791         xdrawglyph(term.line[oldy][oldx], oldx, oldy);
3792
3793         if (IS_SET(MODE_HIDE))
3794                 return;
3795
3796         /* draw the new one */
3797         if (xw.state & WIN_FOCUSED) {
3798                 switch (xw.cursor) {
3799                         case 0: /* Blinking Block */
3800                         case 1: /* Blinking Block (Default) */
3801                         case 2: /* Steady Block */
3802                                 if (IS_SET(MODE_REVERSE)) {
3803                                                 g.mode |= ATTR_REVERSE;
3804                                                 g.fg = defaultcs;
3805                                                 g.bg = defaultfg;
3806                                         }
3807
3808                                 g.mode |= term.line[term.c.y][curx].mode & ATTR_WIDE;
3809                                 xdrawglyph(g, term.c.x, term.c.y);
3810                                 break;
3811                         case 3: /* Blinking Underline */
3812                         case 4: /* Steady Underline */
3813                                 XftDrawRect(xw.draw, &dc.col[defaultcs],
3814                                                 borderpx + curx * xw.cw,
3815                                                 borderpx + (term.c.y + 1) * xw.ch - cursorthickness,
3816                                                 xw.cw, cursorthickness);
3817                                 break;
3818                         case 5: /* Blinking bar */
3819                         case 6: /* Steady bar */
3820                                 XftDrawRect(xw.draw, &dc.col[defaultcs],
3821                                                 borderpx + curx * xw.cw,
3822                                                 borderpx + term.c.y * xw.ch,
3823                                                 cursorthickness, xw.ch);
3824                                 break;
3825                 }
3826         } else {
3827                 XftDrawRect(xw.draw, &dc.col[defaultcs],
3828                                 borderpx + curx * xw.cw,
3829                                 borderpx + term.c.y * xw.ch,
3830                                 xw.cw - 1, 1);
3831                 XftDrawRect(xw.draw, &dc.col[defaultcs],
3832                                 borderpx + curx * xw.cw,
3833                                 borderpx + term.c.y * xw.ch,
3834                                 1, xw.ch - 1);
3835                 XftDrawRect(xw.draw, &dc.col[defaultcs],
3836                                 borderpx + (curx + 1) * xw.cw - 1,
3837                                 borderpx + term.c.y * xw.ch,
3838                                 1, xw.ch - 1);
3839                 XftDrawRect(xw.draw, &dc.col[defaultcs],
3840                                 borderpx + curx * xw.cw,
3841                                 borderpx + (term.c.y + 1) * xw.ch - 1,
3842                                 xw.cw, 1);
3843         }
3844         oldx = curx, oldy = term.c.y;
3845 }
3846
3847
3848 void
3849 xsettitle(char *p)
3850 {
3851         XTextProperty prop;
3852
3853         Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
3854                         &prop);
3855         XSetWMName(xw.dpy, xw.win, &prop);
3856         XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname);
3857         XFree(prop.value);
3858 }
3859
3860 void
3861 xresettitle(void)
3862 {
3863         xsettitle(opt_title ? opt_title : "st");
3864 }
3865
3866 void
3867 redraw(void)
3868 {
3869         tfulldirt();
3870         draw();
3871 }
3872
3873 void
3874 draw(void)
3875 {
3876         drawregion(0, 0, term.col, term.row);
3877         XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, xw.w,
3878                         xw.h, 0, 0);
3879         XSetForeground(xw.dpy, dc.gc,
3880                         dc.col[IS_SET(MODE_REVERSE)?
3881                                 defaultfg : defaultbg].pixel);
3882 }
3883
3884 void
3885 drawregion(int x1, int y1, int x2, int y2)
3886 {
3887         int i, x, y, ox, numspecs;
3888         Glyph base, new;
3889         XftGlyphFontSpec* specs;
3890         int ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN);
3891
3892         if (!(xw.state & WIN_VISIBLE))
3893                 return;
3894
3895         for (y = y1; y < y2; y++) {
3896                 if (!term.dirty[y])
3897                         continue;
3898
3899                 xtermclear(0, y, term.col, y);
3900                 term.dirty[y] = 0;
3901
3902                 specs = term.specbuf;
3903                 numspecs = xmakeglyphfontspecs(specs, &term.line[y][x1], x2 - x1, x1, y);
3904
3905                 i = ox = 0;
3906                 for (x = x1; x < x2 && i < numspecs; x++) {
3907                         new = term.line[y][x];
3908                         if (new.mode == ATTR_WDUMMY)
3909                                 continue;
3910                         if (ena_sel && selected(x, y))
3911                                 new.mode ^= ATTR_REVERSE;
3912                         if (i > 0 && ATTRCMP(base, new)) {
3913                                 xdrawglyphfontspecs(specs, base, i, ox, y);
3914                                 specs += i;
3915                                 numspecs -= i;
3916                                 i = 0;
3917                         }
3918                         if (i == 0) {
3919                                 ox = x;
3920                                 base = new;
3921                         }
3922                         i++;
3923                 }
3924                 if (i > 0)
3925                         xdrawglyphfontspecs(specs, base, i, ox, y);
3926         }
3927         xdrawcursor();
3928 }
3929
3930 void
3931 expose(XEvent *ev)
3932 {
3933         redraw();
3934 }
3935
3936 void
3937 visibility(XEvent *ev)
3938 {
3939         XVisibilityEvent *e = &ev->xvisibility;
3940
3941         MODBIT(xw.state, e->state != VisibilityFullyObscured, WIN_VISIBLE);
3942 }
3943
3944 void
3945 unmap(XEvent *ev)
3946 {
3947         xw.state &= ~WIN_VISIBLE;
3948 }
3949
3950 void
3951 xsetpointermotion(int set)
3952 {
3953         MODBIT(xw.attrs.event_mask, set, PointerMotionMask);
3954         XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs);
3955 }
3956
3957 void
3958 xseturgency(int add)
3959 {
3960         XWMHints *h = XGetWMHints(xw.dpy, xw.win);
3961
3962         MODBIT(h->flags, add, XUrgencyHint);
3963         XSetWMHints(xw.dpy, xw.win, h);
3964         XFree(h);
3965 }
3966
3967 void
3968 focus(XEvent *ev)
3969 {
3970         XFocusChangeEvent *e = &ev->xfocus;
3971
3972         if (e->mode == NotifyGrab)
3973                 return;
3974
3975         if (ev->type == FocusIn) {
3976                 XSetICFocus(xw.xic);
3977                 xw.state |= WIN_FOCUSED;
3978                 xseturgency(0);
3979                 if (IS_SET(MODE_FOCUS))
3980                         ttywrite("\033[I", 3);
3981         } else {
3982                 XUnsetICFocus(xw.xic);
3983                 xw.state &= ~WIN_FOCUSED;
3984                 if (IS_SET(MODE_FOCUS))
3985                         ttywrite("\033[O", 3);
3986         }
3987 }
3988
3989 int
3990 match(uint mask, uint state)
3991 {
3992         return mask == XK_ANY_MOD || mask == (state & ~ignoremod);
3993 }
3994
3995 void
3996 numlock(const Arg *dummy)
3997 {
3998         term.numlock ^= 1;
3999 }
4000
4001 char*
4002 kmap(KeySym k, uint state)
4003 {
4004         Key *kp;
4005         int i;
4006
4007         /* Check for mapped keys out of X11 function keys. */
4008         for (i = 0; i < LEN(mappedkeys); i++) {
4009                 if (mappedkeys[i] == k)
4010                         break;
4011         }
4012         if (i == LEN(mappedkeys)) {
4013                 if ((k & 0xFFFF) < 0xFD00)
4014                         return NULL;
4015         }
4016
4017         for (kp = key; kp < key + LEN(key); kp++) {
4018                 if (kp->k != k)
4019                         continue;
4020
4021                 if (!match(kp->mask, state))
4022                         continue;
4023
4024                 if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0)
4025                         continue;
4026                 if (term.numlock && kp->appkey == 2)
4027                         continue;
4028
4029                 if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0)
4030                         continue;
4031
4032                 if (IS_SET(MODE_CRLF) ? kp->crlf < 0 : kp->crlf > 0)
4033                         continue;
4034
4035                 return kp->s;
4036         }
4037
4038         return NULL;
4039 }
4040
4041 void
4042 kpress(XEvent *ev)
4043 {
4044         XKeyEvent *e = &ev->xkey;
4045         KeySym ksym;
4046         char buf[32], *customkey;
4047         int len;
4048         Rune c;
4049         Status status;
4050         Shortcut *bp;
4051
4052         if (IS_SET(MODE_KBDLOCK))
4053                 return;
4054
4055         len = XmbLookupString(xw.xic, e, buf, sizeof buf, &ksym, &status);
4056         /* 1. shortcuts */
4057         for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
4058                 if (ksym == bp->keysym && match(bp->mod, e->state)) {
4059                         bp->func(&(bp->arg));
4060                         return;
4061                 }
4062         }
4063
4064         /* 2. custom keys from config.h */
4065         if ((customkey = kmap(ksym, e->state))) {
4066                 ttysend(customkey, strlen(customkey));
4067                 return;
4068         }
4069
4070         /* 3. composed string from input method */
4071         if (len == 0)
4072                 return;
4073         if (len == 1 && e->state & Mod1Mask) {
4074                 if (IS_SET(MODE_8BIT)) {
4075                         if (*buf < 0177) {
4076                                 c = *buf | 0x80;
4077                                 len = utf8encode(c, buf);
4078                         }
4079                 } else {
4080                         buf[1] = buf[0];
4081                         buf[0] = '\033';
4082                         len = 2;
4083                 }
4084         }
4085         ttysend(buf, len);
4086 }
4087
4088
4089 void
4090 cmessage(XEvent *e)
4091 {
4092         /*
4093          * See xembed specs
4094          *  http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
4095          */
4096         if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
4097                 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
4098                         xw.state |= WIN_FOCUSED;
4099                         xseturgency(0);
4100                 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
4101                         xw.state &= ~WIN_FOCUSED;
4102                 }
4103         } else if (e->xclient.data.l[0] == xw.wmdeletewin) {
4104                 /* Send SIGHUP to shell */
4105                 kill(pid, SIGHUP);
4106                 exit(0);
4107         }
4108 }
4109
4110 void
4111 cresize(int width, int height)
4112 {
4113         int col, row;
4114
4115         if (width != 0)
4116                 xw.w = width;
4117         if (height != 0)
4118                 xw.h = height;
4119
4120         col = (xw.w - 2 * borderpx) / xw.cw;
4121         row = (xw.h - 2 * borderpx) / xw.ch;
4122
4123         tresize(col, row);
4124         xresize(col, row);
4125         ttyresize();
4126 }
4127
4128 void
4129 resize(XEvent *e)
4130 {
4131         if (e->xconfigure.width == xw.w && e->xconfigure.height == xw.h)
4132                 return;
4133
4134         cresize(e->xconfigure.width, e->xconfigure.height);
4135 }
4136
4137 void
4138 run(void)
4139 {
4140         XEvent ev;
4141         int w = xw.w, h = xw.h;
4142         fd_set rfd;
4143         int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0;
4144         struct timespec drawtimeout, *tv = NULL, now, last, lastblink;
4145         long deltatime;
4146
4147         /* Waiting for window mapping */
4148         do {
4149                 XNextEvent(xw.dpy, &ev);
4150                 /*
4151                  * XFilterEvent is required to be called after you using XOpenIM,
4152                  * this is not unnecessary.It does not only filter the key event,
4153                  * but some clientmessage for input method as well.
4154                  */
4155                 if (XFilterEvent(&ev, None))
4156                         continue;
4157                 if (ev.type == ConfigureNotify) {
4158                         w = ev.xconfigure.width;
4159                         h = ev.xconfigure.height;
4160                 }
4161         } while (ev.type != MapNotify);
4162
4163         ttynew();
4164         cresize(w, h);
4165
4166         clock_gettime(CLOCK_MONOTONIC, &last);
4167         lastblink = last;
4168
4169         for (xev = actionfps;;) {
4170                 FD_ZERO(&rfd);
4171                 FD_SET(cmdfd, &rfd);
4172                 FD_SET(xfd, &rfd);
4173
4174                 if (pselect(MAX(xfd, cmdfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {
4175                         if (errno == EINTR)
4176                                 continue;
4177                         die("select failed: %s\n", strerror(errno));
4178                 }
4179                 if (FD_ISSET(cmdfd, &rfd)) {
4180                         ttyread();
4181                         if (blinktimeout) {
4182                                 blinkset = tattrset(ATTR_BLINK);
4183                                 if (!blinkset)
4184                                         MODBIT(term.mode, 0, MODE_BLINK);
4185                         }
4186                 }
4187
4188                 if (FD_ISSET(xfd, &rfd))
4189                         xev = actionfps;
4190
4191                 clock_gettime(CLOCK_MONOTONIC, &now);
4192                 drawtimeout.tv_sec = 0;
4193                 drawtimeout.tv_nsec =  (1000 * 1E6)/ xfps;
4194                 tv = &drawtimeout;
4195
4196                 dodraw = 0;
4197                 if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) {
4198                         tsetdirtattr(ATTR_BLINK);
4199                         term.mode ^= MODE_BLINK;
4200                         lastblink = now;
4201                         dodraw = 1;
4202                 }
4203                 deltatime = TIMEDIFF(now, last);
4204                 if (deltatime > 1000 / (xev ? xfps : actionfps)) {
4205                         dodraw = 1;
4206                         last = now;
4207                 }
4208
4209                 if (dodraw) {
4210                         while (XPending(xw.dpy)) {
4211                                 XNextEvent(xw.dpy, &ev);
4212                                 if (XFilterEvent(&ev, None))
4213                                         continue;
4214                                 if (handler[ev.type])
4215                                         (handler[ev.type])(&ev);
4216                         }
4217
4218                         draw();
4219                         XFlush(xw.dpy);
4220
4221                         if (xev && !FD_ISSET(xfd, &rfd))
4222                                 xev--;
4223                         if (!FD_ISSET(cmdfd, &rfd) && !FD_ISSET(xfd, &rfd)) {
4224                                 if (blinkset) {
4225                                         if (TIMEDIFF(now, lastblink) \
4226                                                         > blinktimeout) {
4227                                                 drawtimeout.tv_nsec = 1000;
4228                                         } else {
4229                                                 drawtimeout.tv_nsec = (1E6 * \
4230                                                         (blinktimeout - \
4231                                                         TIMEDIFF(now,
4232                                                                 lastblink)));
4233                                         }
4234                                         drawtimeout.tv_sec = \
4235                                             drawtimeout.tv_nsec / 1E9;
4236                                         drawtimeout.tv_nsec %= (long)1E9;
4237                                 } else {
4238                                         tv = NULL;
4239                                 }
4240                         }
4241                 }
4242         }
4243 }
4244
4245 void
4246 usage(void)
4247 {
4248         die("%s " VERSION " (c) 2010-2015 st engineers\n"
4249         "usage: st [-a] [-v] [-c class] [-f font] [-g geometry] [-o file]\n"
4250         "          [-i] [-t title] [-w windowid] [-e command ...] [command ...]\n"
4251         "       st [-a] [-v] [-c class] [-f font] [-g geometry] [-o file]\n"
4252         "          [-i] [-t title] [-w windowid] [-l line] [stty_args ...]\n",
4253         argv0);
4254 }
4255
4256 int
4257 main(int argc, char *argv[])
4258 {
4259         uint cols = 80, rows = 24;
4260
4261         xw.l = xw.t = 0;
4262         xw.isfixed = False;
4263         xw.cursor = 0;
4264
4265         ARGBEGIN {
4266         case 'a':
4267                 allowaltscreen = 0;
4268                 break;
4269         case 'c':
4270                 opt_class = EARGF(usage());
4271                 break;
4272         case 'e':
4273                 if (argc > 0)
4274                         --argc, ++argv;
4275                 goto run;
4276         case 'f':
4277                 opt_font = EARGF(usage());
4278                 break;
4279         case 'g':
4280                 xw.gm = XParseGeometry(EARGF(usage()),
4281                                 &xw.l, &xw.t, &cols, &rows);
4282                 break;
4283         case 'i':
4284                 xw.isfixed = 1;
4285                 break;
4286         case 'o':
4287                 opt_io = EARGF(usage());
4288                 break;
4289         case 'l':
4290                 opt_line = EARGF(usage());
4291                 break;
4292         case 't':
4293                 opt_title = EARGF(usage());
4294                 break;
4295         case 'w':
4296                 opt_embed = EARGF(usage());
4297                 break;
4298         case 'v':
4299         default:
4300                 usage();
4301         } ARGEND;
4302
4303 run:
4304         if (argc > 0) {
4305                 /* eat all remaining arguments */
4306                 opt_cmd = argv;
4307                 if (!opt_title && !opt_line)
4308                         opt_title = basename(xstrdup(argv[0]));
4309         }
4310         setlocale(LC_CTYPE, "");
4311         XSetLocaleModifiers("");
4312         tnew(MAX(cols, 1), MAX(rows, 1));
4313         xinit();
4314         selinit();
4315         run();
4316
4317         return 0;
4318 }