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