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