using _NET_WM_NAME for setting window title (it works with utf-8 strings)
[k8sterm.git] / src / sterm.c
blob6d527612142f8cadea29d132ef9a1278e64ce743
1 /* See LICENSE for licence details. */
2 #define VERSION "0.3.0"
4 #ifdef _XOPEN_SOURCE
5 # undef _XOPEN_SOURCE
6 #endif
7 #define _XOPEN_SOURCE 600
9 #include <alloca.h>
10 #include <ctype.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <iconv.h>
14 #include <limits.h>
15 #include <locale.h>
16 #include <pty.h>
17 #include <stdarg.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <strings.h>
22 #include <signal.h>
23 #include <sys/ioctl.h>
24 #include <sys/select.h>
25 #include <sys/stat.h>
26 #include <sys/time.h>
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #include <time.h>
30 #include <unistd.h>
31 #include <X11/Xatom.h>
32 #include <X11/Xlib.h>
33 #include <X11/Xutil.h>
34 #include <X11/cursorfont.h>
35 #include <X11/keysym.h>
38 //#define DUMP_KEYSYMS
40 //#define KEYPAD_DUMP
42 //#define DUMP_PROG_OUTPUT
43 //#define DUMP_PROG_INPUT
45 //#define DUMP_IO_READ
46 //#define DUMP_IO_WRITE
48 #if defined(DUMP_IO_READ) || defined(DUMP_IO_WRITE)
49 # define DUMP_IO
50 #endif
52 #ifdef KEYPAD_DUMP
53 # define DUMP_KEYPAD_SWITCH(strflag,strstate) do { fprintf(stderr, "KEYPAD %s (%s)\n", (strstate), (strflag)); } while (0)
54 #else
55 # define DUMP_KEYPAD_SWITCH(strflag,strstate) ((void)(sizeof(strflag)+sizeof(strstate)))
56 #endif
59 ////////////////////////////////////////////////////////////////////////////////
60 #define USAGE \
61 "sterm " VERSION " (c) 2010-2012 st engineers and Ketmar // Vampire Avalon\n" \
62 "usage: sterm [-t title] [-c class] [-w windowid] [-T termname] [-C config_file] [-v] [-e command...]\n"
65 ////////////////////////////////////////////////////////////////////////////////
66 #define FONT "-*-liberation mono-medium-*-*-*-17-*-*-*-*-*-koi8-u"
67 #define BOLDFONT "-*-liberation mono-medium-*-*-*-17-*-*-*-*-*-koi8-u"
68 #define FONTTAB "-*-helvetica-*-*-*-*-12-*-*-*-*-*-*-*"
70 #define FONT "-*-terminus-bold-*-*-*-20-*-*-*-*-*-koi8-u"
71 #define BOLDFONT "-*-terminus-bold-*-*-*-20-*-*-*-*-*-koi8-u"
75 /* Space in pixels around the terminal buffer */
76 #define BORDER (2)
79 /* Default shell to use if SHELL is not set in the env */
80 #define SHELL "/bin/sh"
83 /* Terminal colors (16 first used in escape sequence) */
84 static const char *defcolornames[] = {
85 #if 1
86 /* 8 normal colors */
87 "black",
88 "red3",
89 "green3",
90 "yellow3",
91 "blue2",
92 "magenta3",
93 "cyan3",
94 "gray90",
95 /* 8 bright colors */
96 "gray50",
97 "red",
98 "green",
99 "yellow",
100 "#5c5cff",
101 "magenta",
102 "cyan",
103 "white",
104 #else
105 /* 8 normal colors */
106 "#000000",
107 "#b21818",
108 "#18b218",
109 "#b26818",
110 "#1818b2",
111 "#b218b2",
112 "#18b2b2",
113 "#b2b2b2",
114 /* 8 bright colors */
115 "#686868",
116 "#ff5454",
117 "#54ff54",
118 "#ffff54",
119 "#5454ff",
120 "#ff54ff",
121 "#54ffff",
122 "#ffffff",
123 #endif
127 /* more colors can be added after 255 to use with DefaultXX */
128 static const char *defextcolornames[] = {
129 "#cccccc", /* 256 */
130 "#333333", /* 257 */
131 /* root terminal fg and bg */
132 "#809a70", /* 258 */
133 "#002000", /* 259 */
134 /* bold and underline */
135 "#00afaf", /* 260 */
136 "#00af00", /* 261 */
140 /* Default colors (colorname index) foreground, background, cursor, unfocused cursor */
141 #define DEFAULT_FG (7)
142 #define DEFAULT_BG (0)
143 #define DEFAULT_CS (256)
144 #define DEFAULT_UCS (257)
146 #define TNAME "xterm"
148 /* double-click timeout (in milliseconds) between clicks for selection */
149 #define DOUBLECLICK_TIMEOUT (300)
150 #define TRIPLECLICK_TIMEOUT (2*DOUBLECLICK_TIMEOUT)
151 #define SELECT_TIMEOUT 20 /* 20 ms */
152 #define DRAW_TIMEOUT 18 /* 18 ms */
154 #define TAB (8)
157 ////////////////////////////////////////////////////////////////////////////////
158 #define MAX_COLOR (511)
160 /* XEMBED messages */
161 #define XEMBED_FOCUS_IN (4)
162 #define XEMBED_FOCUS_OUT (5)
165 /* Arbitrary sizes */
166 #define ESC_TITLE_SIZ (256)
167 #define ESC_BUF_SIZ (256)
168 #define ESC_ARG_SIZ (16)
169 #define DRAW_BUF_SIZ (2048)
170 #define UTF_SIZ (4)
171 #define OBUFSIZ (256)
172 #define WBUFSIZ (256)
175 /* masks for key translation */
176 #define XK_NO_MOD (UINT_MAX)
177 #define XK_ANY_MOD (0)
180 /* misc utility macros */
181 #define SERRNO strerror(errno)
182 #define MIN(a, b) ((a) < (b) ? (a) : (b))
183 #define MAX(a, b) ((a) < (b) ? (b) : (a))
184 #define LEN(a) (sizeof(a)/sizeof(a[0]))
185 #define DEFAULT(a, b) (a) = ((a) ? (a) : (b))
186 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
187 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
188 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
189 #define IS_SET(flag) (term->mode&(flag))
190 #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000+(t1.tv_usec-t2.tv_usec)/1000)
191 #define X2COL(x) (((x)-opt_border)/xw.cw)
192 #define Y2ROW(y) (((y)-opt_border)/xw.ch-(term!=NULL?term->topline:0))
193 #define IS_GFX(mode) (((mode)&(ATTR_GFX|ATTR_G1)) == ATTR_GFX || ((mode)&(ATTR_GFX1|ATTR_G1)) == (ATTR_GFX1|ATTR_G1))
196 ////////////////////////////////////////////////////////////////////////////////
197 enum glyph_attribute {
198 ATTR_NULL = 0x00,
199 ATTR_REVERSE = 0x01,
200 ATTR_UNDERLINE = 0x02,
201 ATTR_BOLD = 0x04,
202 ATTR_GFX = 0x08,
203 ATTR_DEFFG = 0x10,
204 ATTR_DEFBG = 0x20,
205 ATTR_G1 = 0x40,
206 ATTR_GFX1 = 0x80,
209 enum cursor_movement {
210 CURSOR_UP,
211 CURSOR_DOWN,
212 CURSOR_LEFT,
213 CURSOR_RIGHT,
214 CURSOR_SAVE,
215 CURSOR_LOAD
218 enum cursor_state {
219 CURSOR_DEFAULT = 0,
220 CURSOR_HIDE = 1,
221 CURSOR_WRAPNEXT = 2
224 enum glyph_state {
225 GLYPH_SET = 0x01, /* for selection only */
226 GLYPH_DIRTY = 0x02,
230 enum term_mode {
231 MODE_WRAP = 0x01,
232 MODE_INSERT = 0x02,
233 MODE_APPKEYPAD = 0x04,
234 MODE_ALTSCREEN = 0x08,
235 MODE_CRLF = 0x10,
236 MODE_MOUSEBTN = 0x20,
237 MODE_MOUSEMOTION = 0x40,
238 MODE_MOUSE = 0x20|0x40,
239 MODE_REVERSE = 0x80,
242 enum escape_state {
243 ESC_START = 0x01,
244 ESC_CSI = 0x02,
245 ESC_OSC = 0x04,
246 ESC_TITLE = 0x08,
247 ESC_ALTCHARSET = 0x10,
248 ESC_HASH = 0x20,
249 ESC_PERCENT = 0x40,
250 ESC_ALTG1 = 0x80,
253 enum window_state {
254 WIN_VISIBLE = 0x01,
255 WIN_REDRAW = 0x02,
256 WIN_FOCUSED = 0x04,
259 /* bit macro */
260 #undef B0
261 enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 };
264 ////////////////////////////////////////////////////////////////////////////////
265 typedef unsigned char uchar;
266 typedef unsigned int uint;
267 typedef unsigned long ulong;
268 typedef unsigned short ushort;
271 typedef struct __attribute__((packed)) {
272 char c[UTF_SIZ]; /* character code */
273 uchar mode; /* attribute flags */
274 ushort fg; /* foreground */
275 ushort bg; /* background */
276 uchar state; /* state flags */
277 } Glyph;
280 typedef Glyph *Line;
282 typedef struct {
283 Glyph attr; /* current char attributes */
284 int x;
285 int y;
286 char state;
287 } TCursor;
290 /* CSI Escape sequence structs */
291 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
292 typedef struct {
293 char buf[ESC_BUF_SIZ]; /* raw string */
294 int len; /* raw string length */
295 char priv;
296 int arg[ESC_ARG_SIZ];
297 int narg; /* nb of args */
298 char mode;
299 } CSIEscape;
302 /* Purely graphic info */
303 typedef struct {
304 Display *dpy;
305 Colormap cmap;
306 Window win;
307 Atom xembed;
308 XIM xim;
309 XIC xic;
310 int scr;
311 int w; /* window width */
312 int h; /* window height */
313 int bufw; /* pixmap width */
314 int bufh; /* pixmap height */
315 int ch; /* char height */
316 int cw; /* char width */
317 char state; /* focus, redraw, visible */
319 int tch; /* tab text char height */
320 Pixmap pictab;
321 int tabheight;
322 //struct timeval lastdraw;
323 } XWindow;
326 /* TODO: use better name for vars... */
327 typedef struct {
328 int mode;
329 int bx, by;
330 int ex, ey;
331 struct { int x, y; } b, e;
332 char *clip;
333 Atom xtarget;
334 struct timeval tclick1;
335 struct timeval tclick2;
336 } Selection;
339 /* Drawing Context */
340 typedef struct {
341 ulong *col; //[LEN(colorname) < 256 ? 256 : LEN(colorname)];
342 GC gc;
343 struct {
344 int ascent;
345 int descent;
346 short lbearing;
347 short rbearing;
348 XFontSet set;
349 } font[3];
350 } DC;
353 /* Internal representation of the screen */
354 typedef struct {
355 int cmdfd;
356 int dead;
358 int row; /* nb row */
359 int col; /* nb col */
360 int topline; /* top line for drawing (0: no history; 1: show one history line; etc) */
361 int linecount; /* full, with history */
362 int maxhistory;/* max history lines; 0: none; <0: infinite */
363 Line *line; /* screen */
364 Line *alt; /* alternate screen */
365 char *dirty; /* dirtyness of lines */
366 TCursor c; /* cursor */
367 int top; /* top scroll limit */
368 int bot; /* bottom scroll limit */
369 int mode; /* terminal mode flags */
370 int esc; /* escape state flags */
372 TCursor csaved; /* saved cursor info */
373 // old cursor position
374 int oldcx;
375 int oldcy;
377 char title[ESC_TITLE_SIZ+1];
378 int titlelen;
380 int mouseob;
381 int mouseox;
382 int mouseoy;
384 char obuf[OBUFSIZ];
385 #ifdef DUMP_PROG_OUTPUT
386 int xobuflen;
387 #endif
388 int obuflen;
390 char ubuf[UTF_SIZ];
391 int ubufpos;
393 char drawbuf[DRAW_BUF_SIZ];
395 char wrbuf[WBUFSIZ];
396 int wrbufsize;
397 int wrbufused;
398 int wrbufpos;
400 CSIEscape escseq;
401 Selection sel;
402 pid_t pid;
403 int lastDrawTime;
405 char *execcmd;
407 Pixmap picbuf;
408 int picbufw;
409 int picbufh;
411 ushort deffg;
412 ushort defbg;
414 int wantRedraw;
415 } Term;
418 ////////////////////////////////////////////////////////////////////////////////
419 /* Globals */
420 static uchar *unimap = NULL; // 0 or 65535 items; 127: special; high bit set: use alt(gfx) mode; 0: empty
422 static char **opt_cmd = NULL;
423 static char *opt_title = NULL;
424 static char *opt_embed = NULL;
425 static char *opt_class = NULL;
426 static char *opt_term = NULL;
427 static char *opt_fontnorm = NULL;
428 static char *opt_fontbold = NULL;
429 static char *opt_fonttab = NULL;
430 static char *opt_shell = NULL;
431 static char *opt_colornames[512];
432 static int opt_term_locked = 0;
433 static int opt_doubleclick_timeout = DOUBLECLICK_TIMEOUT;
434 static int opt_tripleclick_timeout = TRIPLECLICK_TIMEOUT;
435 static int opt_tabsize = TAB;
436 static int opt_border = BORDER;
437 static int defaultFG = DEFAULT_FG;
438 static int defaultBG = DEFAULT_BG;
439 static int defaultCursorFG = 0;
440 static int defaultCursorBG = DEFAULT_CS;
441 static int defaultCursorInactiveFG = 0;
442 static int defaultCursorInactiveBG = DEFAULT_UCS;
443 static int defaultBoldFG = -1;
444 static int defaultUnderlineFG = -1;
445 static int normalTabFG = 258;
446 static int normalTabBG = 257;
447 static int activeTabFG = 258;
448 static int activeTabBG = 0;
449 static int opt_maxhistory = 512;
452 static Term **term_array = NULL;
453 static int term_count = 0;
454 static int term_array_size = 0;
455 static Term *term; // current terminal
456 static int termidx; // current terminal index; DON'T RELAY ON IT!
457 static int updateTabBar;
459 static DC dc;
460 static XWindow xw;
462 static Atom XA_VT_SELECTION;
463 static Atom XA_CLIPBOARD;
464 static Atom XA_UTF8;
465 static Atom XA_TARGETS;
466 static Atom XA_NETWM_NAME;
469 ////////////////////////////////////////////////////////////////////////////////
470 typedef struct {
471 KeySym src;
472 KeySym dst;
473 } KeyTransDef;
476 static KeyTransDef *keytrans = NULL;
477 static int keytrans_size = 0;
478 static int keytrans_used = 0;
481 typedef struct {
482 KeySym key;
483 uint mask;
484 int kp;
485 char *str;
486 } KeyInfoDef;
489 static KeyInfoDef *keybinds = NULL;
490 static int keybinds_size = 0;
491 static int keybinds_used = 0;
493 static KeyInfoDef *keymap = NULL;
494 static int keymap_size = 0;
495 static int keymap_used = 0;
498 ////////////////////////////////////////////////////////////////////////////////
499 static void executeCommands (const char *str);
501 static void ttyresize (void);
502 static void tputc (const char *c); // `c` is utf-8
503 static void ttywrite (const char *s, size_t n);
504 static void tsetdirt (int top, int bot);
505 static void tfulldirt (void);
506 static void draw (int forced);
507 static void xseturgency (int add);
510 static inline void ttywritestr (const char *s) { if (s != NULL && s[0]) ttywrite(s, strlen(s)); }
512 static void xfixsel (void);
515 ////////////////////////////////////////////////////////////////////////////////
517 static void trimstr (char *s) {
518 char *e;
520 while (*s && isspace(*s)) ++s;
521 for (e = s+strlen(s); e > s; --e) if (!isspace(e[-1])) break;
522 if (e <= s) *s = 0; else *e = 0;
526 // parse the argument list
527 // return error message or NULL
528 // format:
529 // '*': skip
530 // 's': string (char *)
531 // 'i': integer (int *)
532 // 'b': boolean (int *)
533 // '|': optional arguments follows
534 // '.': stop parsing, ignore rest
535 // 'R': stop parsing, set rest ptr (char *)
536 // string modifiers (also for 'R'):
537 // '!' -- don't allow empty strings
538 // '-' -- trim spaces
539 // int modifiers:
540 // {lo,hi}
541 // {,hi}
542 // {lo}
543 // WARNING! `line` will be modified!
544 // WARNING! string pointers will point INSIDE `line`, so don't discard it!
545 // UGLY! REWRITE!
546 const char *iniParseArguments (char *line, const char *fmt, ...) {
547 va_list ap;
548 int inOptional = 0;
550 if (line == NULL) return "alas";
551 trimstr(line);
552 va_start(ap, fmt);
553 while (*fmt) {
554 char spec = *fmt++, *args;
556 if (spec == '|') { inOptional = 1; continue; }
557 if (spec == '.') { va_end(ap); return NULL; }
559 while (*line && isspace(*line)) ++line;
560 if (*line == '#') *line = 0;
562 if (spec == 'R') {
563 char **p = va_arg(ap, char **);
564 int noempty = 0;
566 while (*fmt) {
567 if (*fmt == '!') { ++fmt; noempty = 1; }
568 else if (*fmt == '-') { ++fmt; trimstr(line); }
569 else break;
571 if (noempty && !line[0]) return "invalid empty arg";
572 if (p != NULL) *p = line;
573 va_end(ap);
574 return NULL;
577 if (!line[0]) {
578 // end of line, stop right here
579 va_end(ap);
580 if (!inOptional) return "out of args";
581 return NULL;
584 args = line;
586 char *dest = args, qch = '#';
587 int n;
589 if (line[0] == '"' || line[0] == '\'') qch = *line++;
591 while (*line && *line != qch) {
592 if (qch == '#' && isspace(*line)) break;
594 if (*line == '\\') {
595 switch (*(++line)) {
596 case 'n': *dest++ = '\n'; ++line; break;
597 case 'r': *dest++ = '\r'; ++line; break;
598 case 't': *dest++ = '\t'; ++line; break;
599 case 'a': *dest++ = '\a'; ++line; break;
600 case 'e': *dest++ = '\x1b'; ++line; break; // esc
601 case 's': *dest++ = ' '; ++line; break;
602 case 'x': // hex
603 ++line;
604 if (!isxdigit(*line)) { va_end(ap); return "invalid hex escape"; }
605 n = toupper(*line)-'0'; if (n > 9) n -= 7;
606 ++line;
607 if (isxdigit(*line)) {
608 int b = toupper(*line)-'0'; if (b > 9) b -= 7;
610 n = (n*16)+b;
611 ++line;
613 *dest++ = n;
614 break;
615 case '0': // octal
616 n = 0;
617 for (int f = 0; f < 4; ++f) {
618 if (*line < '0' || *line > '7') break;
619 n = (n*8)+(line[0]-'0');
620 if (n > 255) { va_end(ap); return "invalid oct escape"; }
621 ++line;
623 if (n == 0) { va_end(ap); return "invalid oct escape"; }
624 *dest++ = n;
625 break;
626 case '1'...'9': // decimal
627 n = 0;
628 for (int f = 0; f < 3; ++f) {
629 if (*line < '0' || *line > '9') break;
630 n = (n*8)+(line[0]-'0');
631 if (n > 255) { va_end(ap); return "invalid dec escape"; }
632 ++line;
634 if (n == 0) { va_end(ap); return "invalid oct escape"; }
635 *dest++ = n;
636 break;
637 default:
638 *dest++ = *line++;
639 break;
641 } else {
642 *dest++ = *line++;
645 if (qch != '#') {
646 if (*line != qch) return "unfinished string";
647 ++line;
648 } else if (*line != '#') ++line;
649 *dest = 0;
651 // now process and convert argument
652 switch (spec) {
653 case '*': /* skip */
654 break;
655 case 's': { /* string */
656 int noempty = 0, trim = 0;
657 char **p;
659 for (;;) {
660 if (*fmt == '!') { noempty = 1; ++fmt; }
661 else if (*fmt == '-') { trim = 1; ++fmt; }
662 else break;
665 if (trim) trimstr(args);
667 if (noempty && !args[0]) { va_end(ap); return "invalid empty string"; }
668 p = va_arg(ap, char **);
669 if (p != NULL) *p = args;
670 } break;
671 case 'i': /* int */
672 if (!args[0]) {
673 va_end(ap);
674 return "invalid integer";
675 } else {
676 int *p = va_arg(ap, int *);
677 long int n;
678 char *eptr;
680 trimstr(args);
681 n = strtol(args, &eptr, 0);
682 if (*eptr) { va_end(ap); return "invalid integer"; }
684 if (*fmt == '{') {
685 // check min/max
686 int minmax[2], haveminmax[2];
688 haveminmax[0] = haveminmax[1] = 0;
689 minmax[0] = minmax[1] = 0;
690 ++fmt;
691 for (int f = 0; f < 2; ++f) {
692 if (isdigit(*fmt) || *fmt == '-' || *fmt == '+') {
693 int neg = 0;
694 haveminmax[f] = 1;
695 if (*fmt == '-') neg = 1;
696 if (!isdigit(*fmt)) {
697 ++fmt;
698 if (!isdigit(*fmt)) { va_end(ap); return "invalid integer bounds"; }
700 while (isdigit(*fmt)) {
701 minmax[f] = minmax[f]*10+(fmt[0]-'0');
702 ++fmt;
704 if (neg) minmax[f] = -minmax[f];
705 //fprintf(stderr, "got: %d\n", minmax[f]);
707 if (*fmt == ',') {
708 if (f == 1) { va_end(ap); return "invalid integer bounds: extra comma"; }
709 // do nothing, we are happy
710 ++fmt;
711 } else if (*fmt == '}') {
712 // ok, done
713 break;
714 } else { va_end(ap); return "invalid integer bounds"; }
716 if (*fmt != '}') { va_end(ap); return "invalid integer bounds"; }
717 ++fmt;
719 //fprintf(stderr, "b: (%d,%d) (%d,%d)\n", haveminmax[0], minmax[0], haveminmax[1], minmax[1]);
720 if ((haveminmax[0] && n < minmax[0]) || (haveminmax[1] && n > minmax[1])) { va_end(ap); return "integer out of bounds"; }
723 if (p) *p = n;
725 break;
726 case 'b': { /* bool */
727 int *p = va_arg(ap, int *);
729 trimstr(args);
730 if (!args[0]) { va_end(ap); return "invalid boolean"; }
731 if (strcasecmp(args, "true") == 0 || strcasecmp(args, "on") == 0 ||
732 strcasecmp(args, "tan") == 0 || strcasecmp(args, "1") == 0) {
733 if (p) *p = 1;
734 } else if (strcasecmp(args, "false") == 0 || strcasecmp(args, "off") == 0 ||
735 strcasecmp(args, "ona") == 0 || strcasecmp(args, "0") == 0) {
737 if (p) *p = 1;
738 } else {
739 va_end(ap);
740 return "invalid boolean";
742 } break;
743 default:
744 va_end(ap);
745 return "invalid format specifier";
748 va_end(ap);
749 while (*line && isspace(*line)) ++line;
750 if (!line[0] || line[0] == '#') return NULL;
751 return "extra args";
755 ////////////////////////////////////////////////////////////////////////////////
756 // UTF-8
757 static int utf8decode (const char *s, long *u) {
758 uchar c;
759 int n, rtn;
761 rtn = 1;
762 c = *s;
763 if (~c & B7) { /* 0xxxxxxx */
764 *u = c;
765 return rtn;
766 } else if ((c & (B7|B6|B5)) == (B7|B6)) { /* 110xxxxx */
767 *u = c&(B4|B3|B2|B1|B0);
768 n = 1;
769 } else if ((c & (B7|B6|B5|B4)) == (B7|B6|B5)) { /* 1110xxxx */
770 *u = c&(B3|B2|B1|B0);
771 n = 2;
772 } else if ((c & (B7|B6|B5|B4|B3)) == (B7|B6|B5|B4)) { /* 11110xxx */
773 *u = c & (B2|B1|B0);
774 n = 3;
775 } else {
776 goto invalid;
778 ++s;
779 for (int f = n; f > 0; --f, ++rtn, ++s) {
780 c = *s;
781 if ((c & (B7|B6)) != B7) goto invalid; /* 10xxxxxx */
782 *u <<= 6;
783 *u |= c & (B5|B4|B3|B2|B1|B0);
785 if ((n == 1 && *u < 0x80) ||
786 (n == 2 && *u < 0x800) ||
787 (n == 3 && *u < 0x10000) ||
788 (*u >= 0xD800 && *u <= 0xDFFF)) {
789 goto invalid;
791 return rtn;
792 invalid:
793 *u = 0xFFFD;
794 return rtn;
798 static int utf8encode (const long *u, char *s) {
799 uchar *sp;
800 ulong uc;
801 int n;
803 sp = (uchar *)s;
804 uc = *u;
805 if (uc < 0x80) {
806 *sp = uc; /* 0xxxxxxx */
807 return 1;
808 } else if (*u < 0x800) {
809 *sp = (uc >> 6) | (B7|B6); /* 110xxxxx */
810 n = 1;
811 } else if (uc < 0x10000) {
812 *sp = (uc >> 12) | (B7|B6|B5); /* 1110xxxx */
813 n = 2;
814 } else if (uc <= 0x10FFFF) {
815 *sp = (uc >> 18) | (B7|B6|B5|B4); /* 11110xxx */
816 n = 3;
817 } else {
818 goto invalid;
820 ++sp;
821 for (int f = n; f > 0; --f, ++sp) *sp = ((uc >> 6*(f-1)) & (B5|B4|B3|B2|B1|B0)) | B7; /* 10xxxxxx */
822 return n+1;
823 invalid:
824 /* U+FFFD */
825 *s++ = '\xEF';
826 *s++ = '\xBF';
827 *s = '\xBD';
828 return 3;
832 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
833 UTF-8 otherwise return 0 */
834 static int isfullutf8 (const char *s, int b) {
835 uchar *c1, *c2, *c3;
837 c1 = (uchar *) s;
838 c2 = (uchar *) ++s;
839 c3 = (uchar *) ++s;
840 if (b < 1) return 0;
841 if ((*c1&(B7|B6|B5)) == (B7|B6) && b == 1) return 0;
842 if ((*c1&(B7|B6|B5|B4)) == (B7|B6|B5) && (b == 1 || (b == 2 && (*c2&(B7|B6)) == B7))) return 0;
843 if ((*c1&(B7|B6|B5|B4|B3)) == (B7|B6|B5|B4) && (b == 1 || (b == 2 && (*c2&(B7|B6)) == B7) || (b == 3 && (*c2&(B7|B6)) == B7 && (*c3&(B7|B6)) == B7))) return 0;
844 return 1;
848 static int utf8size (const char *s) {
849 uchar c = *s;
851 if (~c&B7) return 1;
852 if ((c&(B7|B6|B5)) == (B7|B6)) return 2;
853 if ((c&(B7|B6|B5|B4)) == (B7|B6|B5)) return 3;
854 return 4;
858 ////////////////////////////////////////////////////////////////////////////////
859 // utilities
860 static char *SPrintfVA (const char *fmt, va_list vaorig) {
861 char *buf = NULL;
862 int olen, len = 128;
864 buf = malloc(len);
865 if (buf == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
866 for (;;) {
867 char *nb;
868 va_list va;
870 va_copy(va, vaorig);
871 olen = vsnprintf(buf, len, fmt, va);
872 va_end(va);
873 if (olen >= 0 && olen < len) return buf;
874 if (olen < 0) olen = len*2-1;
875 nb = realloc(buf, olen+1);
876 if (nb == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
877 buf = nb;
878 len = olen+1;
883 static __attribute__((format(printf,1,2))) char *SPrintf (const char *fmt, ...) {
884 char *buf = NULL;
885 va_list va;
887 va_start(va, fmt);
888 buf = SPrintfVA(fmt, va);
889 va_end(va);
890 return buf;
894 static __attribute__((noreturn)) __attribute__((format(printf,1,2))) void die (const char *errstr, ...) {
895 va_list ap;
897 fprintf(stderr, "FATAL: ");
898 va_start(ap, errstr);
899 vfprintf(stderr, errstr, ap);
900 va_end(ap);
901 fprintf(stderr, "\n");
902 exit(EXIT_FAILURE);
906 ////////////////////////////////////////////////////////////////////////////////
907 static void fixWindowTitle (const Term *t) {
908 const char *title = (t != NULL) ? t->title : NULL;
910 if (title == NULL || !title[0]) {
911 title = opt_title;
912 if (title == NULL) title = "";
914 XStoreName(xw.dpy, xw.win, title);
915 XChangeProperty(xw.dpy, xw.win, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, (const unsigned char *)title, strlen(title));
919 static void switchToTerm (int idx, int redraw) {
920 if (idx >= 0 && idx < term_count && term_array[idx] != NULL && term_array[idx] != term) {
921 termidx = idx;
922 term = term_array[termidx];
923 xseturgency(0);
924 tfulldirt();
925 fixWindowTitle(term);
926 updateTabBar = 1;
927 if (redraw) draw(1);
928 xfixsel();
929 //fprintf(stderr, "term #%d\n", termidx);
934 static Term *termalloc (void) {
935 Term *t;
937 if (term_count >= term_array_size) {
938 int newsz = (term_count==0) ? 1 : term_array_size+64;
939 Term **n = realloc(term_array, sizeof(Term *)*newsz);
941 if (n == NULL && term_count == 0) die("out of memory!");
942 term_array = n;
943 term_array_size = newsz;
945 if ((t = calloc(1, sizeof(Term))) == NULL) return NULL;
946 t->wrbufsize = WBUFSIZ;
947 t->deffg = defaultFG;
948 t->defbg = defaultBG;
949 t->dead = 1;
950 term_array[term_count++] = t;
951 return t;
955 // newer delete last terminal!
956 static void termfree (int idx) {
957 if (idx >= 0 && idx < term_count && term_array[idx] != NULL) {
958 Term *t = term_array[idx];
960 if (t->pid != 0) {
961 kill(t->pid, SIGKILL);
962 return;
964 if (t->cmdfd >= 0) {
965 close(t->cmdfd);
966 t->cmdfd = -1;
968 if (idx == termidx) {
969 if (term_count > 1) {
970 switchToTerm((idx > 0) ? idx-1 : 1, 0);
971 t->dead = 1;
972 return;
974 term = NULL;
976 for (int y = 0; y < t->row; ++y) free(t->alt[y]);
977 for (int y = 0; y < t->linecount; ++y) {
978 //fprintf(stderr, "y=%d\n", y);
979 free(t->line[y]);
981 free(t->dirty);
982 free(t->alt);
983 free(t->line);
984 if (t->execcmd != NULL) free(t->execcmd);
985 // condense array
986 if (termidx > idx) {
987 // not current, and current at the right
988 --termidx;
990 for (int f = idx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
991 --term_count;
992 XFreePixmap(xw.dpy, t->picbuf);
993 free(t);
994 updateTabBar = 1;
995 draw(1);
1000 static void termcleanup (void) {
1001 int f = 0;
1003 while (f < term_count) {
1004 if (term_array[f]->dead) {
1005 termfree(f);
1006 } else {
1007 ++f;
1013 //FIXME: is it safe to assume that signal interrupted main program?
1014 static void sigchld (int a) {
1015 //if (waitpid(term->pid, &stat, 0) < 0) die("waiting for pid %hd failed: %s", term->pid, SERRNO);
1016 for (;;) {
1017 int stat = 0;
1018 pid_t res = waitpid(-1, &stat, WNOHANG);
1020 if (res == (pid_t)-1 || res == 0) break;
1021 //if (WIFEXITED(stat)) exit(WEXITSTATUS(stat));
1022 //exit(EXIT_FAILURE);
1023 for (int f = 0; f < term_count; ++f) {
1024 if (term_array[f]->pid == res) {
1025 // this terminal should die
1026 //if (term_count == 1) exit(0);
1027 //close(term_array[f]->cmdfd);
1028 //term_array[f]->cmdfd = -1;
1029 term_array[f]->dead = 1;
1030 term_array[f]->pid = 0;
1031 updateTabBar = 1;
1032 break;
1036 signal(SIGCHLD, sigchld);
1040 ////////////////////////////////////////////////////////////////////////////////
1041 static void keytrans_reset (void) {
1042 if (keytrans) free(keytrans);
1043 keytrans = NULL;
1044 keytrans_size = 0;
1045 keytrans_used = 0;
1049 static void keytrans_add (const char *src, const char *dst) {
1050 KeySym kssrc = XStringToKeysym(src);
1051 KeySym ksdst = XStringToKeysym(dst);
1053 if (kssrc == NoSymbol) die("invalid keysym: '%s'", src);
1054 if (ksdst == NoSymbol) die("invalid keysym: '%s'", dst);
1055 if (kssrc == ksdst) return; // idiot
1057 for (int f = 0; f < keytrans_used; ++f) {
1058 if (keytrans[f].src == kssrc) {
1059 // replace
1060 keytrans[f].dst = ksdst;
1061 return;
1065 if (keytrans_used >= keytrans_size) {
1066 int newsize = keytrans_size+64;
1067 KeyTransDef *n = realloc(keytrans, sizeof(KeyTransDef)*newsize);
1069 if (n == NULL) die("out of memory");
1070 keytrans_size = newsize;
1071 keytrans = n;
1073 keytrans[keytrans_used].src = kssrc;
1074 keytrans[keytrans_used].dst = ksdst;
1075 ++keytrans_used;
1079 ////////////////////////////////////////////////////////////////////////////////
1080 static void parsekeyname (const char *str, KeySym *ks, uint *mask, int *kp) {
1081 char *s = alloca(strlen(str)+1);
1083 if (s == NULL) die("out of memory");
1084 strcpy(s, str);
1085 *kp = 0;
1086 *ks = NoSymbol;
1087 *mask = XK_NO_MOD;
1089 while (*s) {
1090 char *e, oc;
1091 uint mm = 0;
1092 int mod = 1;
1094 while (*s && isspace(*s)) ++s;
1095 for (e = s; *e && !isspace(*e) && *e != '+'; ++e) ;
1096 oc = *e; *e = 0;
1098 if (strcasecmp(s, "alt") == 0) mm = Mod1Mask;
1099 else if (strcasecmp(s, "win") == 0) mm = Mod4Mask;
1100 else if (strcasecmp(s, "ctrl") == 0) mm = ControlMask;
1101 else if (strcasecmp(s, "shift") == 0) mm = ShiftMask;
1102 else if (strcasecmp(s, "any") == 0) mm = XK_NO_MOD; //!
1103 else if (strcasecmp(s, "kpad") == 0) *kp = 1;
1104 else {
1105 mod = 0;
1106 if ((*ks = XStringToKeysym(s)) == NoSymbol) break;
1107 //fprintf(stderr, "[%s]\n", s);
1110 *e = oc;
1111 s = e;
1112 while (*s && isspace(*s)) ++s;
1113 if (mod) {
1114 if (*s != '+') { *ks = NoSymbol; break; }
1115 ++s;
1116 if (mm != 0) {
1117 if (mm == XK_NO_MOD) *mask = XK_ANY_MOD;
1118 else if (*mask == XK_NO_MOD) *mask = mm;
1119 else if (*mask != XK_ANY_MOD) *mask |= mm;
1121 } else {
1122 if (*s) { *ks = NoSymbol; break; }
1125 if (*ks == NoSymbol) die("invalid key name: '%s'", str);
1126 //fprintf(stderr, "mask=0x%08x, kp=%d\n", *mask, *kp);
1130 ////////////////////////////////////////////////////////////////////////////////
1131 static void keybinds_reset (void) {
1132 if (keybinds) free(keybinds);
1133 keybinds = NULL;
1134 keybinds_size = 0;
1135 keybinds_used = 0;
1139 static void keybind_add (const char *key, const char *act) {
1140 KeySym ks;
1141 uint mask;
1142 int kp;
1144 parsekeyname(key, &ks, &mask, &kp);
1146 for (int f = 0; f < keybinds_used; ++f) {
1147 if (keybinds[f].key == ks && keybinds[f].mask == mask) {
1148 // replace or remove
1149 free(keybinds[f].str);
1150 if (act == NULL || !act[0]) {
1151 // remove
1152 for (int c = f+1; c < keybinds_used; ++c) keybinds[c-1] = keybinds[c];
1153 } else {
1154 // replace
1155 if ((keybinds[f].str = strdup(act)) == NULL) die("out of memory");
1157 return;
1161 if (keybinds_used >= keybinds_size) {
1162 int newsize = keybinds_size+64;
1163 KeyInfoDef *n = realloc(keybinds, sizeof(KeyInfoDef)*newsize);
1165 if (n == NULL) die("out of memory");
1166 keybinds_size = newsize;
1167 keybinds = n;
1169 keybinds[keybinds_used].key = ks;
1170 keybinds[keybinds_used].mask = mask;
1171 keybinds[keybinds_used].kp = 0;
1172 if ((keybinds[keybinds_used].str = strdup(act)) == NULL) die("out of memory");
1173 ++keybinds_used;
1177 ////////////////////////////////////////////////////////////////////////////////
1178 static void keymap_reset (void) {
1179 if (keymap) free(keymap);
1180 keymap = NULL;
1181 keymap_size = 0;
1182 keymap_used = 0;
1186 static void keymap_add (const char *key, const char *act) {
1187 KeySym ks;
1188 uint mask;
1189 int kp;
1191 parsekeyname(key, &ks, &mask, &kp);
1193 for (int f = 0; f < keymap_used; ++f) {
1194 if (keymap[f].key == ks && keymap[f].mask == mask && keymap[f].kp == kp) {
1195 // replace or remove
1196 free(keymap[f].str);
1197 if (act == NULL) {
1198 // remove
1199 for (int c = f+1; c < keymap_used; ++c) keymap[c-1] = keymap[c];
1200 } else {
1201 // replace
1202 if ((keymap[f].str = strdup(act)) == NULL) die("out of memory");
1204 return;
1208 if (keymap_used >= keymap_size) {
1209 int newsize = keymap_size+128;
1210 KeyInfoDef *n = realloc(keymap, sizeof(KeyInfoDef)*newsize);
1212 if (n == NULL) die("out of memory");
1213 keymap_size = newsize;
1214 keymap = n;
1216 keymap[keymap_used].key = ks;
1217 keymap[keymap_used].mask = mask;
1218 keymap[keymap_used].kp = kp;
1219 if ((keymap[keymap_used].str = strdup(act)) == NULL) die("out of memory");
1220 ++keymap_used;
1224 ////////////////////////////////////////////////////////////////////////////////
1225 // locale conversions
1226 static iconv_t icFromLoc;
1227 static iconv_t icToLoc;
1228 static int needConversion = 0;
1231 static void initLCConversion (void) {
1232 const char *lct = setlocale(LC_CTYPE, NULL);
1233 char *cstr;
1235 needConversion = 0;
1236 if (strrchr(lct, '.')) lct = strrchr(lct, '.')+1;
1237 if (strcasecmp(lct, "utf8") == 0 || strcasecmp(lct, "utf-8") == 0) return;
1238 //fprintf(stderr, "locale: [%s]\n", lct);
1239 icFromLoc = iconv_open("UTF-8", lct);
1240 if (icFromLoc == (iconv_t)-1) die("can't initialize locale conversion");
1241 cstr = SPrintf("%s//TRANSLIT", lct);
1242 icToLoc = iconv_open(cstr, "UTF-8");
1243 free(cstr);
1244 if (icToLoc == (iconv_t)-1) die("can't initialize locale conversion");
1245 needConversion = 1;
1249 static int loc2utf (char *dest, const char *src, int len) {
1250 if (needConversion) {
1251 char *ibuf, *obuf;
1252 size_t il, ol, ool;
1254 ibuf = (char *)src;
1255 obuf = dest;
1256 il = len;
1257 ool = ol = il*4;
1258 il = iconv(icFromLoc, &ibuf, &il, &obuf, &ol);
1259 if (il == (size_t)-1) return 0;
1260 return ool-ol;
1261 } else {
1262 if (len > 0) memmove(dest, src, len);
1263 return len;
1268 static int utf2loc (char *dest, const char *src, int len) {
1269 if (needConversion) {
1270 char *ibuf, *obuf;
1271 size_t il, ol, ool;
1273 ibuf = (char *)src;
1274 obuf = dest;
1275 il = len;
1276 ool = ol = il*4;
1277 il = iconv(icToLoc, &ibuf, &il, &obuf, &ol);
1278 if (il == (size_t)-1) return 0;
1279 return ool-ol;
1280 } else {
1281 if (len > 0) memmove(dest, src, len);
1282 return len;
1287 ////////////////////////////////////////////////////////////////////////////////
1288 // getticks
1289 static struct timespec mclk_sttime; // starting time of monotonic clock
1292 static void mclock_init (void) {
1293 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &mclk_sttime);
1297 static int mclock_ticks (void) {
1298 struct timespec tp;
1300 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &tp);
1301 tp.tv_sec -= mclk_sttime.tv_sec;
1302 return tp.tv_sec*1000+(tp.tv_nsec/1000000);
1306 ////////////////////////////////////////////////////////////////////////////////
1307 // selection
1308 static void inline markDirty (int lineno, int flag) {
1309 if (term != NULL && lineno >= 0 && lineno < term->row) {
1310 term->dirty[lineno] |= flag;
1311 term->wantRedraw = 1;
1316 static void selinit (void) {
1317 memset(&term->sel.tclick1, 0, sizeof(term->sel.tclick1));
1318 memset(&term->sel.tclick2, 0, sizeof(term->sel.tclick2));
1319 term->sel.mode = 0;
1320 term->sel.bx = -1;
1321 term->sel.clip = NULL;
1322 term->sel.xtarget = XA_UTF8;
1323 if (term->sel.xtarget == None) term->sel.xtarget = XA_STRING;
1327 static inline int selected (int x, int y) {
1328 if (term->sel.ey == y && term->sel.by == y) {
1329 int bx = MIN(term->sel.bx, term->sel.ex);
1330 int ex = MAX(term->sel.bx, term->sel.ex);
1332 return BETWEEN(x, bx, ex);
1334 return
1335 ((term->sel.b.y < y && y < term->sel.e.y) || (y == term->sel.e.y && x <= term->sel.e.x)) ||
1336 (y == term->sel.b.y && x >= term->sel.b.x && (x <= term->sel.e.x || term->sel.b.y != term->sel.e.y));
1340 static void getbuttoninfo (XEvent *e, int *b, int *x, int *y) {
1341 if (b) *b = e->xbutton.button;
1342 *x = X2COL(e->xbutton.x);
1343 *y = Y2ROW(e->xbutton.y);
1344 term->sel.b.x = term->sel.by < term->sel.ey ? term->sel.bx : term->sel.ex;
1345 term->sel.b.y = MIN(term->sel.by, term->sel.ey);
1346 term->sel.e.x = term->sel.by < term->sel.ey ? term->sel.ex : term->sel.bx;
1347 term->sel.e.y = MAX(term->sel.by, term->sel.ey);
1351 static void mousereport (XEvent *e) {
1352 int x = X2COL(e->xbutton.x);
1353 int y = Y2ROW(e->xbutton.y);
1354 int button = e->xbutton.button;
1355 int state = e->xbutton.state;
1356 //char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
1357 char buf[16];
1359 if (term == NULL) return;
1360 if (x < 0 || x >= term->col || y < 0 || y >= term->row) return;
1361 sprintf(buf, "\x1b[M%c%c%c", 0, 32+x+1, 32+y+1);
1362 /* from urxvt */
1363 if (e->xbutton.type == MotionNotify) {
1364 if (!IS_SET(MODE_MOUSEMOTION) || (x == term->mouseox && y == term->mouseoy)) return;
1365 button = term->mouseob+32;
1366 term->mouseox = x;
1367 term->mouseoy = y;
1368 } else if (e->xbutton.type == ButtonRelease || button == AnyButton) {
1369 button = 3;
1370 } else {
1371 button -= Button1;
1372 if (button >= 3) button += 64-3;
1373 if (e->xbutton.type == ButtonPress) {
1374 term->mouseob = button;
1375 term->mouseox = x;
1376 term->mouseoy = y;
1379 buf[3] = 32+button+(state & ShiftMask ? 4 : 0)+(state & Mod4Mask ? 8 : 0)+(state & ControlMask ? 16 : 0);
1380 ttywrite(buf, 6);
1384 static void xfixsel (void) {
1385 if (term == NULL) return;
1386 if (term->sel.clip != NULL) {
1387 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
1388 XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, xw.win, CurrentTime);
1389 } else {
1390 XSetSelectionOwner(xw.dpy, XA_PRIMARY, None, CurrentTime);
1391 XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, None, CurrentTime);
1393 XFlush(xw.dpy);
1397 static void xsetsel (char *str) {
1398 /* register the selection for both the clipboard and the primary */
1399 if (term == NULL) return;
1400 free(term->sel.clip);
1401 term->sel.clip = str;
1402 xfixsel();
1403 //fprintf(stderr, "[%s]\n", str);
1407 static void selcopy (void) {
1408 char *str, *ptr;
1409 int x, y, bufsize, is_selected = 0;
1411 if (term == NULL || term->sel.bx == -1) {
1412 str = NULL;
1413 } else {
1414 bufsize = (term->col+1)*(term->sel.e.y-term->sel.b.y+1)*UTF_SIZ;
1415 ptr = str = malloc(bufsize);
1416 /* append every set & selected glyph to the selection */
1417 for (y = 0; y < term->row; ++y) {
1418 markDirty(y, 1);
1419 for (x = 0; x < term->col; ++x) {
1420 is_selected = selected(x, y);
1421 if (is_selected) term->line[y][x].state |= GLYPH_DIRTY;
1422 if ((term->line[y][x].state & GLYPH_SET) && is_selected) {
1423 int size = utf8size(term->line[y][x].c);
1424 memcpy(ptr, term->line[y][x].c, size);
1425 ptr += size;
1428 /* \n at the end of every selected line except for the last one */
1429 if (is_selected && y < term->sel.e.y) {
1430 markDirty(y, 2);
1431 *ptr++ = '\n';
1434 *ptr = 0;
1436 xsetsel(str);
1440 static void selnotify (XEvent *e) {
1441 ulong nitems, ofs, rem;
1442 int format;
1443 uchar *data;
1444 Atom type;
1445 XSelectionEvent *se = (XSelectionEvent *)e;
1446 int isutf8;
1448 if (term == NULL) return;
1451 char *name;
1453 fprintf(stderr, "selnotify!\n");
1455 name = se->selection != None ? XGetAtomName(se->display, se->selection) : NULL;
1456 fprintf(stderr, " selection: [%s]\n", name);
1457 if (name != NULL) XFree(name);
1459 name = se->target != None ? XGetAtomName(se->display, se->target) : NULL;
1460 fprintf(stderr, " target: [%s]\n", name);
1461 if (name != NULL) XFree(name);
1463 name = se->property != None ? XGetAtomName(se->display, se->property) : NULL;
1464 fprintf(stderr, " property: [%s]\n", name);
1465 if (name != NULL) XFree(name);
1469 if (se->property != XA_VT_SELECTION) return;
1471 fprintf(stderr, "selection:\n");
1472 fprintf(stderr, " primary: %d\n", se->selection == XA_PRIMARY);
1473 fprintf(stderr, " secondary: %d\n", se->selection == XA_SECONDARY);
1474 fprintf(stderr, " clipboard: %d\n", se->selection == XA_CLIPBOARD);
1475 fprintf(stderr, " vtsel: %d\n", se->selection == XA_VT_SELECTION);
1476 fprintf(stderr, "target:\n");
1477 fprintf(stderr, " primary: %d\n", se->target == XA_PRIMARY);
1478 fprintf(stderr, " secondary: %d\n", se->target == XA_SECONDARY);
1479 fprintf(stderr, " clipboard: %d\n", se->target == XA_CLIPBOARD);
1480 fprintf(stderr, " vtsel: %d\n", se->target == XA_VT_SELECTION);
1482 if (se->target == XA_UTF8) {
1483 isutf8 = 1;
1484 } else if (se->target == XA_STRING) {
1485 isutf8 = 0;
1486 } else {
1487 return;
1489 if (!isutf8) return;
1490 ofs = 0;
1491 do {
1492 if (XGetWindowProperty(xw.dpy, xw.win, se->property, ofs, BUFSIZ/4, False, AnyPropertyType, &type, &format, &nitems, &rem, &data)) {
1493 fprintf(stderr, "Clipboard allocation failed\n");
1494 return;
1496 //fprintf(stderr, "nitems=%d; format=%d; rem=%d\n", (int)nitems, format, (int)rem);
1497 ttywrite((const char *)data, nitems*format/8);
1498 XFree(data);
1499 /* number of 32-bit chunks returned */
1500 ofs += nitems*format/32;
1501 } while (rem > 0);
1505 static void selpaste (Atom which) {
1506 //fprintf(stderr, "selpaste %d!\n", which == XA_PRIMARY);
1507 if (term == NULL) return;
1508 //XConvertSelection(xw.dpy, XA_PRIMARY, term->sel.xtarget, XA_PRIMARY, xw.win, CurrentTime);
1509 if (XGetSelectionOwner(xw.dpy, which) != None) {
1510 XConvertSelection(xw.dpy, which, term->sel.xtarget, XA_VT_SELECTION, xw.win, CurrentTime);
1511 return;
1515 char *name = XGetAtomName(xw.dpy, which);
1517 fprintf(stderr, "no selection owner for [%s]!\n", name);
1518 XFree(name);
1521 if (which == XA_PRIMARY) selpaste(XA_SECONDARY);
1522 else if (which == XA_SECONDARY) selpaste(XA_CLIPBOARD);
1526 static void selrequest (XEvent *e) {
1527 XSelectionRequestEvent *xsre;
1528 XSelectionEvent xev;
1530 if (term == NULL) return;
1531 xsre = (XSelectionRequestEvent *)e;
1532 xev.type = SelectionNotify;
1533 xev.requestor = xsre->requestor;
1534 xev.selection = xsre->selection;
1535 xev.target = xsre->target;
1536 xev.time = xsre->time;
1537 /* reject */
1538 xev.property = None;
1541 char *name;
1543 fprintf(stderr, "selrequest!\n");
1545 name = xsre->selection != None ? XGetAtomName(xsre->display, xsre->selection) : NULL;
1546 fprintf(stderr, " selection: [%s]\n", name);
1547 if (name != NULL) XFree(name);
1549 name = xsre->target != None ? XGetAtomName(xsre->display, xsre->target) : NULL;
1550 fprintf(stderr, " target: [%s]\n", name);
1551 if (name != NULL) XFree(name);
1553 name = xsre->property != None ? XGetAtomName(xsre->display, xsre->property) : NULL;
1554 fprintf(stderr, " property: [%s]\n", name);
1555 if (name != NULL) XFree(name);
1558 if (xsre->target == XA_TARGETS) {
1559 /* respond with the supported type */
1560 Atom string = XA_UTF8;
1562 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_ATOM, 32, PropModeReplace, (uchar *)&string, 1);
1563 xev.property = xsre->property;
1564 } else if (xsre->target == XA_UTF8 && term->sel.clip != NULL) {
1565 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_UTF8, 8, PropModeReplace, (uchar *)term->sel.clip, strlen(term->sel.clip));
1566 xev.property = xsre->property;
1567 } else if (xsre->target == XA_STRING && term->sel.clip != NULL) {
1568 char *s = malloc(strlen(term->sel.clip)*4+8);
1570 if (s != NULL) {
1571 int len = utf2loc(s, term->sel.clip, strlen(term->sel.clip));
1573 XChangeProperty(xsre->display, xsre->requestor, xsre->property, xsre->target, 8, PropModeReplace, (uchar *)s, len);
1574 xev.property = xsre->property;
1575 free(s);
1578 /* all done, send a notification to the listener */
1579 if (!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *)&xev)) fprintf(stderr, "Error sending SelectionNotify event\n");
1583 static void selclear (XEvent *e) {
1584 if (term == NULL) return;
1585 term->sel.mode = 0;
1586 term->sel.ex = term->sel.bx = -1;
1587 term->sel.ey = term->sel.by = -1;
1588 tfulldirt();
1589 draw(1);
1593 static void bpress (XEvent *e) {
1594 if (term == NULL) return;
1595 if (e->xbutton.button == Button1 && (e->xbutton.state&ShiftMask) != 0) {
1596 if (term->sel.bx != -1) tsetdirt(term->sel.b.y, term->sel.e.y);
1597 term->sel.mode = 1;
1598 term->sel.ex = term->sel.bx = X2COL(e->xbutton.x);
1599 term->sel.ey = term->sel.by = Y2ROW(e->xbutton.y);
1600 draw(1);
1601 return;
1603 if (e->xbutton.button == Button3 && (e->xbutton.state&ShiftMask) != 0) {
1604 term->sel.bx = -1;
1605 selcopy();
1606 draw(1);
1608 if (IS_SET(MODE_MOUSE)) mousereport(e);
1612 static void brelease (XEvent *e) {
1613 int reportit = 1;
1615 if (term == NULL) return;
1616 if ((e->xbutton.state&ShiftMask) != 0) reportit = 0;
1617 if (reportit && IS_SET(MODE_MOUSE)) {
1618 mousereport(e);
1619 return;
1621 if (e->xbutton.button == Button2) {
1622 selpaste(XA_PRIMARY);
1623 } else if (e->xbutton.button == Button1) {
1624 term->sel.mode = 0;
1625 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey);
1626 if (term->sel.bx == term->sel.ex && term->sel.by == term->sel.ey) {
1627 struct timeval now;
1629 markDirty(term->sel.ey, 2);
1630 term->sel.bx = -1;
1631 gettimeofday(&now, NULL);
1632 if (TIMEDIFF(now, term->sel.tclick2) <= opt_tripleclick_timeout) {
1633 /* triple click on the line */
1634 term->sel.b.x = term->sel.bx = 0;
1635 term->sel.e.x = term->sel.ex = term->col;
1636 term->sel.b.y = term->sel.e.y = term->sel.ey;
1637 selcopy();
1638 } else if (TIMEDIFF(now, term->sel.tclick1) <= opt_doubleclick_timeout) {
1639 /* double click to select word */
1640 term->sel.bx = term->sel.ex;
1641 while (term->sel.bx > 0 && term->line[term->sel.ey][term->sel.bx-1].state & GLYPH_SET && term->line[term->sel.ey][term->sel.bx-1].c[0] != ' ') --term->sel.bx;
1642 term->sel.b.x = term->sel.bx;
1643 while (term->sel.ex < term->col-1 && term->line[term->sel.ey][term->sel.ex+1].state & GLYPH_SET && term->line[term->sel.ey][term->sel.ex+1].c[0] != ' ') ++term->sel.ex;
1644 term->sel.e.x = term->sel.ex;
1645 term->sel.b.y = term->sel.e.y = term->sel.ey;
1646 selcopy();
1648 } else {
1649 selcopy();
1652 memcpy(&term->sel.tclick2, &term->sel.tclick1, sizeof(struct timeval));
1653 gettimeofday(&term->sel.tclick1, NULL);
1654 draw(1);
1658 static void bmotion (XEvent *e) {
1659 if (term == NULL) return;
1660 if (term->sel.mode) {
1661 int oldey = term->sel.ey, oldex = term->sel.ex;
1663 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey);
1664 if (oldey != term->sel.ey || oldex != term->sel.ex) {
1665 int starty = MIN(oldey, term->sel.ey);
1666 int endy = MAX(oldey, term->sel.ey);
1668 tsetdirt(starty, endy);
1669 draw(1);
1671 return;
1673 if (IS_SET(MODE_MOUSE)) {
1674 mousereport(e);
1675 return;
1680 ////////////////////////////////////////////////////////////////////////////////
1681 // tty init
1682 static inline void setWantRedraw (void) { if (term != NULL) term->wantRedraw = 1; }
1686 static void dump (char c) {
1687 static int col;
1689 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
1690 if (++col % 10 == 0) fprintf(stderr, "\n");
1695 static __attribute__((noreturn)) void execsh (const char *str) {
1696 char **args;
1698 if (str == NULL) {
1699 char *envshell = getenv("SHELL");
1701 DEFAULT(envshell, opt_shell);
1702 setenv("TERM", opt_term, 1);
1703 args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL};
1704 } else {
1705 int argc = 0;
1707 args = calloc(32768, sizeof(char *));
1708 if (args == NULL) exit(EXIT_FAILURE);
1709 while (*str) {
1710 const char *b;
1712 while (*str && isspace(*str)) ++str;
1713 if (!str[0]) break;
1715 b = str;
1716 while (*str && !isspace(*str)) {
1717 if (*str++ == '\\') {
1718 if (*str) ++str;
1722 args[argc] = calloc(str-b+1, 1);
1723 memcpy(args[argc], b, str-b);
1726 FILE *fo = fopen("z.log", "a");
1727 fprintf(fo, "%d: [%s]\n", argc, args[argc]);
1728 fclose(fo);
1731 ++argc;
1733 if (argc < 1) exit(EXIT_FAILURE);
1735 execvp(args[0], args);
1736 exit(EXIT_FAILURE);
1740 static int ttynew (Term *term) {
1741 int m, s;
1742 struct winsize w = {term->row, term->col, 0, 0};
1743 static int signalset = 0;
1745 if (openpty(&m, &s, NULL, NULL, &w) < 0) die("openpty failed: %s", SERRNO);
1746 term->cmdfd = m;
1747 ttyresize();
1748 term->cmdfd = -1;
1749 switch (term->pid = fork()) {
1750 case -1: /* error */
1751 fprintf(stderr, "fork failed");
1752 return -1;
1753 case 0: /* child */
1754 setsid(); /* create a new process group */
1755 dup2(s, STDIN_FILENO);
1756 dup2(s, STDOUT_FILENO);
1757 dup2(s, STDERR_FILENO);
1758 if (ioctl(s, TIOCSCTTY, NULL) < 0) die("ioctl TIOCSCTTY failed: %s", SERRNO);
1759 close(s);
1760 close(m);
1761 execsh(term->execcmd);
1762 break;
1763 default: /* master */
1764 close(s);
1765 term->cmdfd = m;
1766 term->dead = 0;
1767 ttyresize();
1768 if (!signalset) { signalset = 1; signal(SIGCHLD, sigchld); }
1769 break;
1771 return 0;
1775 ////////////////////////////////////////////////////////////////////////////////
1776 // tty r/w
1777 static int ttycanread (void) {
1778 for (;;) {
1779 fd_set rfd;
1780 struct timeval timeout = {0};
1782 if (term->dead || term->cmdfd < 0) return 0;
1783 FD_ZERO(&rfd);
1784 FD_SET(term->cmdfd, &rfd);
1785 if (select(term->cmdfd+1, &rfd, NULL, NULL, &timeout) < 0) {
1786 if (errno == EINTR) continue;
1787 die("select failed: %s", SERRNO);
1789 if (FD_ISSET(term->cmdfd, &rfd)) return 1;
1790 break;
1792 return 0;
1796 static int ttycanwrite (void) {
1797 for (;;) {
1798 fd_set wfd;
1799 struct timeval timeout = {0};
1801 if (term->dead || term->cmdfd < 0) return 0;
1802 FD_ZERO(&wfd);
1803 FD_SET(term->cmdfd, &wfd);
1804 if (select(term->cmdfd+1, NULL, &wfd, NULL, &timeout) < 0) {
1805 if (errno == EINTR) continue;
1806 die("select failed: %s", SERRNO);
1808 if (FD_ISSET(term->cmdfd, &wfd)) return 1;
1809 break;
1811 return 0;
1815 #ifdef DUMP_IO
1816 static void wrstr (const char *s, int len) {
1817 if (s == NULL) return;
1818 while (len-- > 0) {
1819 unsigned char c = (unsigned char)(*s++);
1821 if (c < 32) fprintf(stderr, "{%u}", c); else fwrite(&c, 1, 1, stderr);
1824 #endif
1827 static void ttyread (void) {
1828 char *ptr;
1829 int left;
1831 /* append read bytes to unprocessed bytes */
1832 if (term == NULL || term->dead || term->cmdfd < 0) return;
1833 #ifdef DUMP_PROG_OUTPUT
1834 term->xobuflen = term->obuflen;
1835 #endif
1836 left = OBUFSIZ-term->obuflen;
1837 //dlogf("0: ttyread before: free=%d, used=%d", left, term->obuflen);
1838 while (left > 0 && ttycanread()) {
1839 int ret;
1841 //if ((ret = read(term->cmdfd, term->obuf+term->obuflen, 1)) < 0) die("Couldn't read from shell: %s", SERRNO);
1842 if ((ret = read(term->cmdfd, term->obuf+term->obuflen, left)) < 0) {
1843 //fprintf(stderr, "Warning: couldn't read from tty #%d: %s\n", termidx, SERRNO);
1844 break;
1846 term->obuflen += ret;
1847 left -= ret;
1849 //dlogf("1: ttyread after: free=%d, used=%d", left, term->obuflen);
1850 /* process every complete utf8 char */
1851 #ifdef DUMP_PROG_OUTPUT
1853 FILE *fo = fopen("zlogo.log", "ab");
1854 if (fo) {
1855 fwrite(term->obuf+term->xobuflen, term->obuflen-term->xobuflen, 1, fo);
1856 fclose(fo);
1859 #endif
1860 ptr = term->obuf;
1861 if (needConversion) {
1862 // need conversion from locale to utf-8
1863 //fprintf(stderr, "buf: %d bytes\n", term->obuflen);
1864 while (term->obuflen > 0) {
1865 char obuf[UTF_SIZ+1];
1866 int len;
1868 len = loc2utf(obuf, ptr, 1);
1869 #ifdef DUMP_IO_READ
1871 fprintf(stderr, "rdc: [");
1872 wrstr(ptr, 1);
1873 fprintf(stderr, "] --> [");
1874 wrstr(obuf, len);
1875 fprintf(stderr, "]\n");
1876 fflush(stderr);
1878 #endif
1879 if (len > 0) {
1880 obuf[len] = 0;
1881 tputc(obuf);
1883 ++ptr;
1884 --term->obuflen;
1886 term->obuflen = 0;
1887 } else {
1888 // don't do any conversion
1889 while (term->obuflen >= UTF_SIZ || isfullutf8(ptr, term->obuflen)) {
1890 long utf8c;
1891 char s[UTF_SIZ+1];
1892 int charsize = utf8decode(ptr, &utf8c); /* returns size of utf8 char in bytes */
1893 int len;
1895 len = utf8encode(&utf8c, s);
1896 #ifdef DUMP_IO_READ
1898 fprintf(stderr, "rdx: [");
1899 wrstr(s, len);
1900 fprintf(stderr, "]\n");
1901 fflush(stderr);
1903 #endif
1904 if (len > 0) {
1905 s[len] = 0;
1906 tputc(s);
1908 ptr += charsize;
1909 term->obuflen -= charsize;
1911 //dlogf("2: ttyread afterproc: used=%d", term->obuflen);
1913 /* keep any uncomplete utf8 char for the next call */
1914 if (term->obuflen > 0) memmove(term->obuf, ptr, term->obuflen);
1918 static void ttyflushwrbuf (void) {
1919 if (term == NULL || term->dead || term->cmdfd < 0) return;
1920 if (term->wrbufpos >= term->wrbufused) {
1921 term->wrbufpos = term->wrbufused = 0;
1922 return;
1924 //dlogf("0: ttyflushwrbuf before: towrite=%d", term->wrbufused-term->wrbufpos);
1925 while (term->wrbufpos < term->wrbufused && ttycanwrite()) {
1926 int ret;
1928 if ((ret = write(term->cmdfd, term->wrbuf+term->wrbufpos, term->wrbufused-term->wrbufpos)) == -1) {
1929 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
1931 term->wrbufpos += ret;
1933 if (term->wrbufpos > 0) {
1934 int left = term->wrbufused-term->wrbufpos;
1936 if (left < 1) {
1937 // write buffer is empty
1938 term->wrbufpos = term->wrbufused = 0;
1939 } else {
1940 memmove(term->wrbuf, term->wrbuf+term->wrbufpos, left);
1941 term->wrbufpos = 0;
1942 term->wrbufused = left;
1945 //dlogf("1: ttyflushwrbuf after: towrite=%d", term->wrbufused-term->wrbufpos);
1949 // convert char to locale and write it
1950 static void ttywriterawchar (const char *s, int len) {
1951 char loc[16];
1952 int clen;
1954 if (s == NULL || len < 1) return;
1955 clen = utf2loc(loc, s, len);
1956 if (clen < 1) return;
1957 #ifdef DUMP_IO_WRITE
1959 fprintf(stderr, "wrc: [");
1960 wrstr(s, len);
1961 fprintf(stderr, "] --> [");
1962 wrstr(loc, clen);
1963 fprintf(stderr, "]\n");
1964 fflush(stderr);
1966 #endif
1968 while (term->wrbufused+clen >= term->wrbufsize) {
1969 //FIXME: make write buffer dynamic?
1970 // force write at least one char
1971 //dlogf("ttywrite: forced write");
1972 if (write(term->cmdfd, term->wrbuf+term->wrbufpos, 1) == -1) {
1973 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
1974 } else {
1975 ++term->wrbufpos;
1977 ttyflushwrbuf(); // make room for char
1979 memcpy(term->wrbuf+term->wrbufused, loc, clen);
1980 term->wrbufused += clen;
1984 static void ttywrite (const char *s, size_t n) {
1985 if (term == NULL || term->dead || term->cmdfd < 0) return;
1986 #ifdef DUMP_PROG_INPUT
1987 if (s != NULL && n > 0) {
1988 FILE *fo = fopen("zlogw.log", "ab");
1989 if (fo) {
1990 fwrite(s, n, 1, fo);
1991 fclose(fo);
1994 #endif
1995 //ttyflushwrbuf();
1996 if (s != NULL && n > 0) {
1997 while (n > 0) {
1998 unsigned char c = (unsigned char)(s[0]);
2000 if (term->ubufpos > 0 && isfullutf8(term->ubuf, term->ubufpos)) {
2001 // have complete char
2002 ttywriterawchar(term->ubuf, term->ubufpos);
2003 term->ubufpos = 0;
2004 continue;
2007 if (term->ubufpos == 0) {
2008 // new char
2009 if (c < 128) {
2010 ttywriterawchar(s, 1);
2011 } else if ((c&0xc0) == 0xc0) {
2012 // new utf-8 char
2013 term->ubuf[term->ubufpos++] = *s;
2014 } else {
2015 // ignore unsynced utf-8
2017 ++s;
2018 --n;
2019 continue;
2021 // char continues
2022 if (c < 128 || term->ubufpos >= UTF_SIZ || (c&0xc0) == 0xc0) {
2023 // discard previous utf-8, it's bad
2024 term->ubufpos = 0;
2025 continue;
2027 // collect
2028 term->ubuf[term->ubufpos++] = *s;
2029 ++s;
2030 --n;
2031 if (isfullutf8(term->ubuf, term->ubufpos)) {
2032 // have complete char
2033 ttywriterawchar(term->ubuf, term->ubufpos);
2034 term->ubufpos = 0;
2038 ttyflushwrbuf();
2042 ////////////////////////////////////////////////////////////////////////////////
2043 // tty resize ioctl
2044 static void ttyresize (void) {
2045 struct winsize w;
2047 if (term != NULL && term->cmdfd >= 0) {
2048 w.ws_row = term->row;
2049 w.ws_col = term->col;
2050 w.ws_xpixel = w.ws_ypixel = 0;
2051 if (ioctl(term->cmdfd, TIOCSWINSZ, &w) < 0) fprintf(stderr, "Warning: couldn't set window size: %s\n", SERRNO);
2052 setWantRedraw();
2057 ////////////////////////////////////////////////////////////////////////////////
2058 // tty utilities
2059 static void csidump (void) {
2060 printf("ESC");
2061 for (int f = 1; f < term->escseq.len; ++f) {
2062 uint c = (term->escseq.buf[f]&0xff);
2064 if (isprint(c)) putchar(c);
2065 else if (c == '\n') printf("(\\n)");
2066 else if (c == '\r') printf("(\\r)");
2067 else if (c == 0x1b) printf("(\\e)");
2068 else printf("(%02x)", c);
2070 putchar('\n');
2074 static void tsetdirt (int top, int bot) {
2075 LIMIT(top, 0, term->row-1);
2076 LIMIT(bot, 0, term->row-1);
2077 for (int y = top; y <= bot; ++y) markDirty(y, 2);
2081 static void tfulldirt (void) {
2082 tsetdirt(0, term->row-1);
2086 static void tmoveto (int x, int y) {
2087 LIMIT(x, 0, term->col-1);
2088 LIMIT(y, 0, term->row-1);
2089 term->c.state &= ~CURSOR_WRAPNEXT;
2090 if (term->c.x != x || term->c.y != y) {
2091 term->c.x = x;
2092 term->c.y = y;
2093 setWantRedraw();
2098 static void tclearregion (int x1, int y1, int x2, int y2) {
2099 int temp;
2101 if (x1 > x2) { temp = x1; x1 = x2; x2 = temp; }
2102 if (y1 > y2) { temp = y1; y1 = y2; y2 = temp; }
2103 LIMIT(x1, 0, term->col-1);
2104 LIMIT(x2, 0, term->col-1);
2105 LIMIT(y1, 0, term->row-1);
2106 LIMIT(y2, 0, term->row-1);
2107 for (int y = y1; y <= y2; ++y) {
2108 markDirty(y, (x1 <= 0 && x2 >= term->col-1) ? 2 : 1);
2109 for (int x = x1; x <= x2; ++x) {
2110 term->line[y][x].fg = term->c.attr.fg;
2111 term->line[y][x].bg = term->c.attr.bg;
2112 term->line[y][x].state = GLYPH_DIRTY;
2113 term->line[y][x].mode = ATTR_NULL|(term->c.attr.mode&(ATTR_DEFFG|ATTR_DEFBG));
2114 term->line[y][x].c[0] = ' ';
2120 static void tcursor (int mode) {
2121 if (mode == CURSOR_SAVE) {
2122 term->csaved = term->c;
2123 } else if (mode == CURSOR_LOAD) {
2124 term->c = term->csaved;
2125 tmoveto(term->c.x, term->c.y);
2126 setWantRedraw();
2131 static void treset (void) {
2132 Glyph g;
2134 term->c = (TCursor){{
2135 .mode = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG,
2136 .fg = 0,
2137 .bg = 0
2138 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
2139 term->c.attr.fg = term->deffg;
2140 term->c.attr.bg = term->defbg;
2142 g.state = GLYPH_DIRTY;
2143 g.mode = term->c.attr.mode;
2144 g.fg = term->c.attr.fg;
2145 g.bg = term->c.attr.bg;
2146 g.c[0] = ' ';
2147 g.c[1] = 0;
2149 term->top = 0;
2150 term->bot = term->row-1;
2151 term->mode = MODE_WRAP | MODE_MOUSEBTN;
2152 //tclearregion(0, 0, term->col-1, term->row-1);
2153 for (int y = 0; y < term->row; ++y) {
2154 markDirty(y, 2);
2155 for (int x = 0; x < term->col; ++x) term->alt[y][x] = term->line[y][x] = g;
2157 for (int y = term->row; y < term->linecount; ++y) {
2158 for (int x = 0; x < term->col; ++x) term->line[y][x] = g;
2160 tcursor(CURSOR_SAVE);
2161 term->topline = 0;
2165 static int tinitialize (int col, int row) {
2166 //memset(term, 0, sizeof(Term));
2167 term->wrbufsize = WBUFSIZ;
2168 term->deffg = term->deffg;
2169 term->defbg = term->defbg;
2170 term->row = row;
2171 term->col = col;
2172 term->dirty = calloc(term->row, sizeof(*term->dirty));
2173 term->maxhistory = opt_maxhistory;
2174 term->linecount = term->maxhistory+term->row;
2175 term->line = calloc(term->linecount, sizeof(Line));
2176 term->alt = calloc(term->row, sizeof(Line));
2177 for (int y = 0; y < term->linecount; ++y) term->line[y] = calloc(term->col, sizeof(Glyph));
2178 for (int y = 0; y < term->row; ++y) term->alt[y] = calloc(term->col, sizeof(Glyph));
2179 /* setup screen */
2180 treset();
2181 return 1;
2185 static void tswapscreen (void) {
2186 for (int f = 0; f < term->row; ++f) {
2187 Line t = term->line[f];
2189 term->line[f] = term->alt[f];
2190 term->alt[f] = t;
2192 term->mode ^= MODE_ALTSCREEN;
2193 tfulldirt();
2197 //FIXME: will not work for history
2198 static void selscroll (int orig, int n) {
2199 if (term->sel.bx == -1) return;
2201 if (BETWEEN(term->sel.by, orig, term->bot) || BETWEEN(term->sel.ey, orig, term->bot)) {
2202 if ((term->sel.by += n) > term->bot || (term->sel.ey += n) < term->top) {
2203 term->sel.bx = -1;
2204 return;
2206 if (term->sel.by < term->top) {
2207 term->sel.by = term->top;
2208 term->sel.bx = 0;
2210 if (term->sel.ey > term->bot) {
2211 term->sel.ey = term->bot;
2212 term->sel.ex = term->col;
2214 term->sel.b.y = term->sel.by, term->sel.b.x = term->sel.bx;
2215 term->sel.e.y = term->sel.ey, term->sel.e.x = term->sel.ex;
2220 static void tscrolldown (int orig, int n) {
2221 Line temp;
2223 LIMIT(n, 0, term->bot-orig+1);
2224 //fprintf(stderr, "tscrolldown(%d, %d)\n", orig, n);
2225 tclearregion(0, term->bot-n+1, term->col-1, term->bot);
2226 for (int f = term->bot; f >= orig+n; --f) {
2227 temp = term->line[f];
2228 term->line[f] = term->line[f-n];
2229 term->line[f-n] = temp;
2230 markDirty(f, 2);
2231 markDirty(f-n, 2);
2233 selscroll(orig, n);
2237 static void tscrollup (int orig, int n) {
2238 Line temp;
2240 if (term == NULL) return;
2241 LIMIT(n, 0, term->bot-orig+1);
2242 if (n < 1) return;
2243 //fprintf(stderr, "tscrollup(%d, %d)\n", orig, n);
2244 if (n == 1 && orig == 0 && term->bot >= term->row-1 && term->maxhistory > 0) {
2245 Line l = term->line[term->linecount-1];
2247 for (int f = term->linecount-1; f > term->row; --f) term->line[f] = term->line[f-1];
2248 term->line[term->row] = l;
2249 for (int x = 0; x < term->col; ++x) l[x] = term->line[0][x];
2252 tclearregion(0, orig, term->col-1, orig+n-1);
2254 for (int f = orig; f <= term->bot-n; ++f) {
2255 temp = term->line[f];
2256 term->line[f] = term->line[f+n];
2257 term->line[f+n] = temp;
2258 markDirty(f, 2);
2259 markDirty(f+n, 2);
2261 selscroll(orig, -n);
2265 static void tnewline (int first_col) {
2266 int y = term->c.y;
2268 if (y == term->bot) tscrollup(term->top, 1); else ++y;
2269 tmoveto(first_col ? 0 : term->c.x, y);
2273 static void csiparse (void) {
2274 const char *p = term->escseq.buf;
2276 term->escseq.narg = 0;
2277 if (*p == '?') { term->escseq.priv = 1; ++p; }
2278 while (p < term->escseq.buf+term->escseq.len) {
2279 int n = term->escseq.arg[term->escseq.narg];
2281 for (; *p && isdigit(*p); ++p) n = n*10+(p[0]-'0');
2282 term->escseq.arg[term->escseq.narg] = n;
2284 if (*p == ';' && term->escseq.narg+1 < ESC_ARG_SIZ) {
2285 ++term->escseq.narg;
2286 ++p;
2287 } else {
2288 term->escseq.mode = *p;
2289 ++term->escseq.narg;
2290 break;
2296 static void tsetchar (const char *c) {
2297 char ub[UTF_SIZ+1];
2298 int rev = 0, gfx = 0;
2300 if (!needConversion && unimap != NULL) {
2301 long cc;
2303 utf8decode(c, &cc);
2304 if (cc >= 0 && cc <= 65535) {
2305 uchar uc = unimap[cc];
2307 if (uc) {
2308 if (uc == 127) {
2309 // inversed space
2310 rev = 1;
2311 ub[0] = ' ';
2312 } else {
2313 if (uc&0x80) {
2314 ub[0] = (uc&0x7f);
2315 gfx = 1;
2316 } else {
2317 ub[0] = uc&0x7f;
2320 ub[1] = 0;
2321 c = ub;
2326 if (rev || gfx || (term->line[term->c.y][term->c.x].state & GLYPH_SET) == 0 || ATTRCMP(term->line[term->c.y][term->c.x], term->c.attr)) {
2327 term->line[term->c.y][term->c.x] = term->c.attr;
2328 if (rev) term->line[term->c.y][term->c.x].mode ^= ATTR_REVERSE;
2329 if (gfx) term->line[term->c.y][term->c.x].mode |= ATTR_GFX;
2330 } else {
2331 int clen = utf8size(c), olen = utf8size(term->line[term->c.y][term->c.x].c);
2333 if (clen < 1 || (clen == olen && memcmp(c, term->line[term->c.y][term->c.x].c, clen) == 0)) return;
2336 term->line[term->c.y][term->c.x] = term->c.attr;
2337 if (rev) term->line[term->c.y][term->c.x].mode ^= ATTR_REVERSE;
2338 if (gfx) {
2339 if (term->line[term->c.y][term->c.x].mode&ATTR_G1) {
2340 term->line[term->c.y][term->c.x].mode &= ~ATTR_GFX;
2341 term->line[term->c.y][term->c.x].mode |= ATTR_GFX1;
2342 } else {
2343 term->line[term->c.y][term->c.x].mode |= ATTR_GFX;
2344 term->line[term->c.y][term->c.x].mode &= ~ATTR_GFX1;
2348 markDirty(term->c.y, 1);
2349 memcpy(term->line[term->c.y][term->c.x].c, c, UTF_SIZ);
2350 if (IS_GFX(term->line[term->c.y][term->c.x].mode)) {
2351 unsigned char c = (unsigned char)(term->line[term->c.y][term->c.x].c[0]);
2353 if (c > 95 && c < 128) term->line[term->c.y][term->c.x].c[0] -= 95;
2354 else if (c > 127) term->line[term->c.y][term->c.x].c[0] = ' ';
2356 term->line[term->c.y][term->c.x].state = (GLYPH_SET | GLYPH_DIRTY);
2360 static void tfillscreenwithE (void) {
2361 for (int y = 0; y < term->row; ++y) {
2362 markDirty(y, 2);
2363 for (int x = 0; x < term->col; ++x) {
2364 term->line[y][x].state = /*GLYPH_SET |*/ GLYPH_DIRTY;
2365 term->line[y][x].c[0] = 'E';
2366 term->line[y][x].mode = ATTR_NULL;
2372 static void tdeletechar (int n) {
2373 int src = term->c.x+n;
2374 int dst = term->c.x;
2375 int size = term->col-src;
2377 markDirty(term->c.y, 2);
2378 if (src >= term->col) {
2379 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2380 } else {
2381 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2382 tclearregion(term->col-n, term->c.y, term->col-1, term->c.y);
2387 static void tinsertblank (int n) {
2388 int src = term->c.x;
2389 int dst = src+n;
2390 int size = term->col-dst;
2392 markDirty(term->c.y, 2);
2393 if (dst >= term->col) {
2394 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2395 } else {
2396 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2397 tclearregion(src, term->c.y, dst-1, term->c.y);
2402 static void tinsertblankline (int n) {
2403 if (term->c.y < term->top || term->c.y > term->bot) return;
2404 tscrolldown(term->c.y, n);
2408 static void tdeleteline (int n) {
2409 if (term->c.y < term->top || term->c.y > term->bot) return;
2410 tscrollup(term->c.y, n);
2414 static void tsetattr (int *attr, int l) {
2415 for (int f = 0; f < l; ++f) {
2416 switch (attr[f]) {
2417 case 0:
2418 term->c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
2419 term->c.attr.mode |= ATTR_DEFFG | ATTR_DEFBG;
2420 term->c.attr.fg = term->deffg;
2421 term->c.attr.bg = term->defbg;
2422 break;
2423 case 1:
2424 term->c.attr.mode |= ATTR_BOLD;
2425 break;
2426 case 4:
2427 term->c.attr.mode |= ATTR_UNDERLINE;
2428 break;
2429 case 7:
2430 term->c.attr.mode |= ATTR_REVERSE;
2431 break;
2432 case 22:
2433 term->c.attr.mode &= ~ATTR_BOLD;
2434 break;
2435 case 24:
2436 term->c.attr.mode &= ~ATTR_UNDERLINE;
2437 break;
2438 case 27:
2439 term->c.attr.mode &= ~ATTR_REVERSE;
2440 break;
2441 case 38:
2442 if (f+2 < l && attr[f+1] == 5) {
2443 f += 2;
2444 if (BETWEEN(attr[f], 0, 255)) {
2445 term->c.attr.fg = attr[f];
2446 term->c.attr.mode &= ~ATTR_DEFFG;
2447 } else {
2448 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[f]);
2450 } else {
2451 //fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2452 term->c.attr.fg = term->deffg;
2453 term->c.attr.mode |= ATTR_DEFFG;
2455 break;
2456 case 39:
2457 term->c.attr.fg = term->deffg;
2458 term->c.attr.mode |= ATTR_DEFFG;
2459 break;
2460 case 48:
2461 if (f+2 < l && attr[f+1] == 5) {
2462 f += 2;
2463 if (BETWEEN(attr[f], 0, 255)) {
2464 term->c.attr.bg = attr[f];
2465 term->c.attr.mode &= ~ATTR_DEFBG;
2466 } else {
2467 fprintf(stderr, "erresc: bad bgcolor %d\n", attr[f]);
2469 } else {
2470 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2472 break;
2473 case 49:
2474 term->c.attr.bg = term->defbg;
2475 term->c.attr.mode |= ATTR_DEFBG;
2476 break;
2477 default:
2478 if (BETWEEN(attr[f], 30, 37)) { term->c.attr.fg = attr[f]-30; term->c.attr.mode &= ~ATTR_DEFFG; }
2479 else if (BETWEEN(attr[f], 40, 47)) { term->c.attr.bg = attr[f]-40; term->c.attr.mode &= ~ATTR_DEFBG; }
2480 else if (BETWEEN(attr[f], 90, 97)) { term->c.attr.fg = attr[f]-90+8; term->c.attr.mode &= ~ATTR_DEFFG; }
2481 else if (BETWEEN(attr[f], 100, 107)) { term->c.attr.fg = attr[f]-100+8; term->c.attr.mode &= ~ATTR_DEFFG; }
2482 else { fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]); csidump(); }
2483 break;
2489 static void tsetscroll (int t, int b) {
2490 int temp;
2492 LIMIT(t, 0, term->row-1);
2493 LIMIT(b, 0, term->row-1);
2494 if (t > b) {
2495 temp = t;
2496 t = b;
2497 b = temp;
2499 term->top = t;
2500 term->bot = b;
2504 ////////////////////////////////////////////////////////////////////////////////
2505 // esc processing
2506 static void csihandle (void) {
2507 switch (term->escseq.mode) {
2508 case '@': /* ICH -- Insert <n> blank char */
2509 DEFAULT(term->escseq.arg[0], 1);
2510 tinsertblank(term->escseq.arg[0]);
2511 break;
2512 case 'A': /* CUU -- Cursor <n> Up */
2513 case 'e':
2514 DEFAULT(term->escseq.arg[0], 1);
2515 tmoveto(term->c.x, term->c.y-term->escseq.arg[0]);
2516 break;
2517 case 'B': /* CUD -- Cursor <n> Down */
2518 DEFAULT(term->escseq.arg[0], 1);
2519 tmoveto(term->c.x, term->c.y+term->escseq.arg[0]);
2520 break;
2521 case 'C': /* CUF -- Cursor <n> Forward */
2522 case 'a':
2523 DEFAULT(term->escseq.arg[0], 1);
2524 tmoveto(term->c.x+term->escseq.arg[0], term->c.y);
2525 break;
2526 case 'D': /* CUB -- Cursor <n> Backward */
2527 DEFAULT(term->escseq.arg[0], 1);
2528 tmoveto(term->c.x-term->escseq.arg[0], term->c.y);
2529 break;
2530 case 'E': /* CNL -- Cursor <n> Down and first col */
2531 DEFAULT(term->escseq.arg[0], 1);
2532 tmoveto(0, term->c.y+term->escseq.arg[0]);
2533 break;
2534 case 'F': /* CPL -- Cursor <n> Up and first col */
2535 DEFAULT(term->escseq.arg[0], 1);
2536 tmoveto(0, term->c.y-term->escseq.arg[0]);
2537 break;
2538 case 'G': /* CHA -- Move to <col> */
2539 case '`': /* XXX: HPA -- same? */
2540 DEFAULT(term->escseq.arg[0], 1);
2541 tmoveto(term->escseq.arg[0]-1, term->c.y);
2542 break;
2543 case 'H': /* CUP -- Move to <row> <col> */
2544 case 'f': /* XXX: HVP -- same? */
2545 DEFAULT(term->escseq.arg[0], 1);
2546 DEFAULT(term->escseq.arg[1], 1);
2547 tmoveto(term->escseq.arg[1]-1, term->escseq.arg[0]-1);
2548 break;
2549 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
2550 case 'J': /* ED -- Clear screen */
2551 term->sel.bx = -1;
2552 switch (term->escseq.arg[0]) {
2553 case 0: /* below */
2554 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2555 if (term->c.y < term->row-1) tclearregion(0, term->c.y+1, term->col-1, term->row-1);
2556 break;
2557 case 1: /* above */
2558 if (term->c.y > 1) tclearregion(0, 0, term->col-1, term->c.y-1);
2559 tclearregion(0, term->c.y, term->c.x, term->c.y);
2560 break;
2561 case 2: /* all */
2562 tclearregion(0, 0, term->col-1, term->row-1);
2563 break;
2564 default:
2565 goto unknown;
2567 break;
2568 case 'K': /* EL -- Clear line */
2569 switch (term->escseq.arg[0]) {
2570 case 0: /* right */
2571 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2572 break;
2573 case 1: /* left */
2574 tclearregion(0, term->c.y, term->c.x, term->c.y);
2575 break;
2576 case 2: /* all */
2577 tclearregion(0, term->c.y, term->col-1, term->c.y);
2578 break;
2580 break;
2581 case 'S': /* SU -- Scroll <n> line up */
2582 DEFAULT(term->escseq.arg[0], 1);
2583 tscrollup(term->top, term->escseq.arg[0]);
2584 break;
2585 case 'T': /* SD -- Scroll <n> line down */
2586 DEFAULT(term->escseq.arg[0], 1);
2587 tscrolldown(term->top, term->escseq.arg[0]);
2588 break;
2589 case 'L': /* IL -- Insert <n> blank lines */
2590 DEFAULT(term->escseq.arg[0], 1);
2591 tinsertblankline(term->escseq.arg[0]);
2592 break;
2593 case 'l': /* RM -- Reset Mode */
2594 if (term->escseq.priv) {
2595 switch (term->escseq.arg[0]) {
2596 case 1: // 1001 for xterm compatibility
2597 DUMP_KEYPAD_SWITCH("1", "OFF");
2598 term->mode &= ~MODE_APPKEYPAD;
2599 break;
2600 case 5: /* DECSCNM -- Remove reverse video */
2601 if (IS_SET(MODE_REVERSE)) {
2602 term->mode &= ~MODE_REVERSE;
2603 tfulldirt();
2605 break;
2606 case 7: /* autowrap off */
2607 term->mode &= ~MODE_WRAP;
2608 break;
2609 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
2610 break;
2611 case 20: /* non-standard code? */
2612 term->mode &= ~MODE_CRLF;
2613 break;
2614 case 25: /* hide cursor */
2615 term->c.state |= CURSOR_HIDE;
2616 break;
2617 case 1000: /* disable X11 xterm mouse reporting */
2618 term->mode &= ~MODE_MOUSEBTN;
2619 break;
2620 case 1002:
2621 term->mode &= ~MODE_MOUSEMOTION;
2622 break;
2623 case 1049: /* = 1047 and 1048 */
2624 case 47:
2625 case 1047:
2626 if (IS_SET(MODE_ALTSCREEN)) {
2627 tclearregion(0, 0, term->col-1, term->row-1);
2628 tswapscreen();
2630 if (term->escseq.arg[0] != 1049) break;
2631 case 1048:
2632 tcursor(CURSOR_LOAD);
2633 break;
2634 default:
2635 goto unknown;
2637 } else {
2638 switch (term->escseq.arg[0]) {
2639 case 4:
2640 term->mode &= ~MODE_INSERT;
2641 break;
2642 default:
2643 goto unknown;
2646 break;
2647 case 'M': /* DL -- Delete <n> lines */
2648 DEFAULT(term->escseq.arg[0], 1);
2649 tdeleteline(term->escseq.arg[0]);
2650 break;
2651 case 'X': /* ECH -- Erase <n> char */
2652 DEFAULT(term->escseq.arg[0], 1);
2653 tclearregion(term->c.x, term->c.y, term->c.x + term->escseq.arg[0], term->c.y);
2654 break;
2655 case 'P': /* DCH -- Delete <n> char */
2656 DEFAULT(term->escseq.arg[0], 1);
2657 tdeletechar(term->escseq.arg[0]);
2658 break;
2659 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
2660 case 'd': /* VPA -- Move to <row> */
2661 DEFAULT(term->escseq.arg[0], 1);
2662 tmoveto(term->c.x, term->escseq.arg[0]-1);
2663 break;
2664 case 'h': /* SM -- Set terminal mode */
2665 if (term->escseq.priv) {
2666 switch (term->escseq.arg[0]) {
2667 case 1:
2668 DUMP_KEYPAD_SWITCH("1", "ON");
2669 term->mode |= MODE_APPKEYPAD;
2670 break;
2671 case 5: /* DECSCNM -- Reverve video */
2672 if (!IS_SET(MODE_REVERSE)) {
2673 term->mode |= MODE_REVERSE;
2674 tfulldirt();
2676 break;
2677 case 7:
2678 term->mode |= MODE_WRAP;
2679 break;
2680 case 20:
2681 term->mode |= MODE_CRLF;
2682 break;
2683 case 12: /* att610 -- Start blinking cursor (IGNORED) */
2684 /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
2685 if (term->escseq.narg > 1 && term->escseq.arg[1] != 25) break;
2686 case 25:
2687 term->c.state &= ~CURSOR_HIDE;
2688 break;
2689 case 1000: /* 1000,1002: enable xterm mouse report */
2690 term->mode |= MODE_MOUSEBTN;
2691 break;
2692 case 1002:
2693 term->mode |= MODE_MOUSEMOTION;
2694 break;
2695 case 1049: /* = 1047 and 1048 */
2696 case 47:
2697 case 1047:
2698 if (IS_SET(MODE_ALTSCREEN)) tclearregion(0, 0, term->col-1, term->row-1); else tswapscreen();
2699 if (term->escseq.arg[0] != 1049) break;
2700 case 1048:
2701 tcursor(CURSOR_SAVE);
2702 break;
2703 default: goto unknown;
2705 } else {
2706 switch (term->escseq.arg[0]) {
2707 case 4:
2708 term->mode |= MODE_INSERT;
2709 break;
2710 default:
2711 goto unknown;
2714 break;
2715 case 'm': /* SGR -- Terminal attribute (color) */
2716 tsetattr(term->escseq.arg, term->escseq.narg);
2717 break;
2718 case 'r': /* DECSTBM -- Set Scrolling Region */
2719 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
2720 // xterm compatibility
2721 DUMP_KEYPAD_SWITCH("1001", "OFF");
2722 term->mode &= ~MODE_APPKEYPAD;
2723 } else if (term->escseq.priv) {
2724 goto unknown;
2725 } else {
2726 DEFAULT(term->escseq.arg[0], 1);
2727 DEFAULT(term->escseq.arg[1], term->row);
2728 tsetscroll(term->escseq.arg[0]-1, term->escseq.arg[1]-1);
2729 tmoveto(0, 0);
2731 break;
2732 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
2733 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
2734 // xterm compatibility
2735 DUMP_KEYPAD_SWITCH("1001", "ON");
2736 term->mode |= MODE_APPKEYPAD;
2737 } else {
2738 tcursor(CURSOR_SAVE);
2740 break;
2741 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
2742 tcursor(CURSOR_LOAD);
2743 break;
2744 default:
2745 unknown:
2746 fprintf(stderr, "erresc: unknown csi ");
2747 csidump();
2748 break;
2753 static void csireset (void) {
2754 memset(&term->escseq, 0, sizeof(term->escseq));
2758 static void tputtab (void) {
2759 int space = opt_tabsize-term->c.x%opt_tabsize;
2761 if (space > 0) tmoveto(term->c.x+space, term->c.y);
2765 ////////////////////////////////////////////////////////////////////////////////
2766 // put char to output buffer or process command
2767 static void tputc (const char *c) {
2768 char ascii = *c;
2770 if (term->esc & ESC_START) {
2771 if (term->esc & ESC_CSI) {
2772 term->escseq.buf[term->escseq.len++] = ascii;
2773 if (BETWEEN(ascii, 0x40, 0x7E) || term->escseq.len >= ESC_BUF_SIZ) {
2774 term->esc = 0;
2775 csiparse();
2776 csihandle();
2778 } else if (term->esc & ESC_OSC) {
2779 /* TODO: handle other OSC */
2780 if (ascii == ';') {
2781 term->titlelen = 0;
2782 term->esc = ESC_START | ESC_TITLE;
2783 //updateTabBar = 1;
2785 } else if (term->esc & ESC_TITLE) {
2786 if (ascii == '\a' || term->titlelen+1 >= ESC_TITLE_SIZ) {
2787 term->esc = 0;
2788 term->title[term->titlelen] = '\0';
2789 fixWindowTitle(term);
2790 updateTabBar = 1;
2791 } else {
2792 int len = utf8size(c);
2794 if (len > 0 && term->titlelen+len < ESC_TITLE_SIZ) {
2795 memcpy(term->title+term->titlelen, c, len);
2796 term->titlelen += len;
2798 term->title[term->titlelen] = '\0';
2800 } else if (term->esc & ESC_ALTCHARSET) {
2801 switch (ascii) {
2802 case '0': /* Line drawing crap */
2803 term->c.attr.mode |= ATTR_GFX;
2804 break;
2805 case 'B': /* Back to regular text */
2806 term->c.attr.mode &= ~ATTR_GFX;
2807 break;
2808 default:
2809 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2810 term->c.attr.mode &= ~ATTR_GFX;
2811 break;
2813 term->esc = 0;
2814 } else if (term->esc & ESC_ALTG1) {
2815 switch (ascii) {
2816 case '0': /* Line drawing crap */
2817 term->c.attr.mode |= ATTR_GFX1;
2818 break;
2819 case 'B': /* Back to regular text */
2820 term->c.attr.mode &= ~ATTR_GFX1;
2821 break;
2822 default:
2823 fprintf(stderr, "esc unhandled charset: ESC ) %c\n", ascii);
2824 term->c.attr.mode &= ~ATTR_GFX1;
2825 break;
2827 term->esc = 0;
2828 } else if (term->esc & ESC_HASH) {
2829 switch (ascii) {
2830 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
2831 tfillscreenwithE();
2832 break;
2834 term->esc = 0;
2835 } else if (term->esc & ESC_PERCENT) {
2836 term->esc = 0;
2837 } else {
2838 switch (ascii) {
2839 case '[': term->esc |= ESC_CSI; break;
2840 case ']': term->esc |= ESC_OSC; break;
2841 case '(': term->esc |= ESC_ALTCHARSET; break;
2842 case ')': term->esc |= ESC_ALTG1; break;
2843 case '#': term->esc |= ESC_HASH; break;
2844 case '%': term->esc |= ESC_PERCENT; break;
2845 case 'D': /* IND -- Linefeed */
2846 term->esc = 0;
2847 if (term->c.y == term->bot) tscrollup(term->top, 1); else tmoveto(term->c.x, term->c.y+1);
2848 break;
2849 case 'E': /* NEL -- Next line */
2850 term->esc = 0;
2851 tnewline(1); /* always go to first col */
2852 break;
2853 case 'M': /* RI -- Reverse linefeed */
2854 term->esc = 0;
2855 if (term->c.y == term->top) tscrolldown(term->top, 1); else tmoveto(term->c.x, term->c.y-1);
2856 break;
2857 case 'c': /* RIS -- Reset to inital state */
2858 term->esc = 0;
2859 treset();
2860 break;
2861 case '=': /* DECPAM -- Application keypad */
2862 DUMP_KEYPAD_SWITCH("=", "ON");
2863 term->esc = 0;
2864 term->mode |= MODE_APPKEYPAD;
2865 break;
2866 case '>': /* DECPNM -- Normal keypad */
2867 DUMP_KEYPAD_SWITCH(">", "OFF");
2868 term->esc = 0;
2869 term->mode &= ~MODE_APPKEYPAD;
2870 break;
2871 case '7': /* DECSC -- Save Cursor */
2872 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
2873 //TODO?
2874 term->esc = 0;
2875 tcursor(CURSOR_SAVE);
2876 break;
2877 case '8': /* DECRC -- Restore Cursor */
2878 //TODO?
2879 term->esc = 0;
2880 tcursor(CURSOR_LOAD);
2881 break;
2882 case 'Z': /* DEC private identification */
2883 term->esc = 0;
2884 ttywritestr("\x1b[?1;2c");
2885 break;
2886 default:
2887 term->esc = 0;
2888 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X ('%c')\n", (uchar)ascii, isprint(ascii)?ascii:'.');
2889 break;
2892 } else {
2893 if (term->sel.bx != -1 && BETWEEN(term->c.y, term->sel.by, term->sel.ey)) term->sel.bx = -1;
2894 switch (ascii) {
2895 case '\t': tputtab(); break;
2896 case '\b': tmoveto(term->c.x-1, term->c.y); break;
2897 case '\r': tmoveto(0, term->c.y); break;
2898 case '\v': tnewline(0); break;
2899 case '\f': case '\n': tnewline(IS_SET(MODE_CRLF)); break; /* go to first col if the mode is set */
2900 case '\a': if (!(xw.state & WIN_FOCUSED)) xseturgency(1); XBell(xw.dpy, 100); break;
2901 case 14: term->c.attr.mode |= ATTR_G1; break;
2902 case 15: term->c.attr.mode &= ~ATTR_G1; break;
2903 case '\033': csireset(); term->esc = ESC_START; break;
2904 default:
2905 if (needConversion && IS_GFX(term->c.attr.mode)) {
2906 long cc;
2908 utf8decode(c, &cc);
2909 if (cc < 32 || cc >= 127) break; //FIXME: nothing at all
2910 } else {
2911 if ((unsigned char)ascii < 32 || ascii == 127) break; // seems that this chars are empty too
2913 if (IS_SET(MODE_WRAP) && (term->c.state&CURSOR_WRAPNEXT)) tnewline(1); /* always go to first col */
2914 tsetchar(c);
2915 if (term->c.x+1 < term->col) tmoveto(term->c.x+1, term->c.y); else term->c.state |= CURSOR_WRAPNEXT;
2916 break;
2922 ////////////////////////////////////////////////////////////////////////////////
2923 // tty resising
2924 static int tresize (int col, int row) {
2925 int mincol = MIN(col, term->col);
2926 int slide = term->c.y-row+1;
2927 Glyph g;
2929 if (col < 1 || row < 1) return 0;
2931 g.state = GLYPH_DIRTY;
2932 g.mode = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG;
2933 g.fg = term->deffg;
2934 g.bg = term->defbg;
2935 g.c[0] = ' ';
2936 g.c[1] = 0;
2938 if (slide > 0) {
2939 tsetscroll(0, term->row-1);
2940 for (; slide > 0; --slide) tscrollup(0, 1); // to fill history
2943 if (row < term->row) {
2944 /* free unneeded rows */
2945 for (int f = row; f < term->row; ++f) free(term->alt[f]);
2946 for (int f = term->linecount-(term->row-row); f < term->linecount; ++f) free(term->line[f]);
2947 term->linecount -= (term->row-row);
2948 /* resize to new height */
2949 term->alt = realloc(term->alt, row*sizeof(Line));
2950 term->line = realloc(term->line, term->linecount*sizeof(Line));
2951 } else if (row > term->row) {
2952 /* resize to new height */
2953 term->alt = realloc(term->alt, row*sizeof(Line));
2954 term->line = realloc(term->line, (row+term->maxhistory)*sizeof(Line));
2955 /* add more lines */
2956 for (int f = term->row; f < row; ++f) {
2957 term->alt[f] = calloc(col, sizeof(Glyph));
2958 for (int x = 0; x < col; ++x) term->alt[f][x] = g;
2960 for (int f = 0; f < row-term->row; ++f) {
2961 int y = term->linecount++;
2963 term->line[y] = calloc(col, sizeof(Glyph));
2964 for (int x = 0; x < col; ++x) term->line[y][x] = g;
2968 if (row != term->row) {
2969 term->dirty = realloc(term->dirty, row*sizeof(*term->dirty));
2972 /* resize each row to new width, zero-pad if needed */
2973 for (int f = 0; f < term->linecount; ++f) {
2974 term->line[f] = realloc(term->line[f], col*sizeof(Glyph));
2975 for (int x = mincol; x < col; ++x) term->line[f][x] = g;
2976 if (f < row) {
2977 markDirty(f, 2);
2978 term->alt[f] = realloc(term->alt[f], col*sizeof(Glyph));
2979 for (int x = mincol; x < col; ++x) term->alt[f][x] = g;
2982 /* update terminal size */
2983 term->topline = 0;
2984 term->col = col;
2985 term->row = row;
2986 /* make use of the LIMIT in tmoveto */
2987 tmoveto(term->c.x, term->c.y);
2988 /* reset scrolling region */
2989 tsetscroll(0, row-1);
2990 tfulldirt();
2991 return (slide > 0);
2995 static void xresize (int col, int row) {
2996 Pixmap newbuf;
2997 int oldw, oldh;
2999 if (term == NULL) return;
3000 oldw = term->picbufw;
3001 oldh = term->picbufh;
3002 term->picbufw = MAX(1, col*xw.cw);
3003 term->picbufh = MAX(1, row*xw.ch);
3004 newbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3005 XCopyArea(xw.dpy, term->picbuf, newbuf, dc.gc, 0, 0, term->picbufw, term->picbufh, 0, 0);
3006 XFreePixmap(xw.dpy, term->picbuf);
3007 XSetForeground(xw.dpy, dc.gc, dc.col[term->defbg]);
3008 if (term->picbufw > oldw) {
3009 XFillRectangle(xw.dpy, newbuf, dc.gc, oldw, 0, term->picbufw-oldw, MIN(term->picbufh, oldh));
3010 } else if (term->picbufw < oldw && (opt_border > 0 || xw.w > term->picbufw)) {
3011 XClearArea(xw.dpy, xw.win, opt_border+term->picbufw, opt_border, xw.w-term->picbufh-opt_border, opt_border+MIN(term->picbufh, oldh), False);
3013 if (term->picbufh > oldh) {
3014 XFillRectangle(xw.dpy, newbuf, dc.gc, 0, oldh, term->picbufw, term->picbufh-oldh);
3015 } else if (term->picbufh < oldh && (opt_border > 0 || xw.tabheight > 0 || xw.h > term->picbufh)) {
3016 XClearArea(xw.dpy, xw.win, opt_border, opt_border+term->picbufh, xw.w-2*opt_border, xw.h-term->picbufh-opt_border-xw.tabheight, False);
3018 term->picbuf = newbuf;
3019 tfulldirt();
3020 updateTabBar = 1;
3024 ////////////////////////////////////////////////////////////////////////////////
3025 // x11 drawing and utils
3026 static void xloadcols (void) {
3027 int f, r, g, b;
3028 XColor color;
3029 ulong white = WhitePixel(xw.dpy, xw.scr);
3031 dc.col = calloc(MAX_COLOR+1, sizeof(dc.col[0]));
3032 for (f = 0; f <= MAX_COLOR; ++f) dc.col[f] = white;
3033 /* load colors [0-15] */
3034 for (f = 0; f <= 15; ++f) {
3035 const char *cname = opt_colornames[f]!=NULL?opt_colornames[f]:defcolornames[f];
3037 if (!XAllocNamedColor(xw.dpy, xw.cmap, cname, &color, &color)) {
3038 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, cname);
3039 } else {
3040 dc.col[f] = color.pixel;
3043 /* load colors [256-...] */
3044 for (f = 256; f <= MAX_COLOR; ++f) {
3045 const char *cname = opt_colornames[f];
3047 if (cname == NULL) {
3048 if (LEN(defextcolornames) <= f-256) continue;
3049 cname = defextcolornames[f-256];
3051 if (cname == NULL) continue;
3052 if (!XAllocNamedColor(xw.dpy, xw.cmap, cname, &color, &color)) {
3053 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, cname);
3054 } else {
3055 dc.col[f] = color.pixel;
3058 /* load colors [16-255] ; same colors as xterm */
3059 for (f = 16, r = 0; r < 6; ++r) {
3060 for (g = 0; g < 6; ++g) {
3061 for (b = 0; b < 6; ++b) {
3062 if (opt_colornames[f] != NULL) {
3063 if (!XAllocNamedColor(xw.dpy, xw.cmap, opt_colornames[f], &color, &color)) {
3064 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, opt_colornames[f]);
3065 } else {
3066 dc.col[f] = color.pixel;
3068 } else {
3069 color.red = r == 0 ? 0 : 0x3737+0x2828*r;
3070 color.green = g == 0 ? 0 : 0x3737+0x2828*g;
3071 color.blue = b == 0 ? 0 : 0x3737+0x2828*b;
3072 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3073 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3074 } else {
3075 dc.col[f] = color.pixel;
3078 ++f;
3082 for (r = 0; r < 24; ++r, ++f) {
3083 if (opt_colornames[f] != NULL) {
3084 if (!XAllocNamedColor(xw.dpy, xw.cmap, opt_colornames[f], &color, &color)) {
3085 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, opt_colornames[f]);
3086 } else {
3087 dc.col[f] = color.pixel;
3089 } else {
3090 color.red = color.green = color.blue = 0x0808+0x0a0a*r;
3091 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3092 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3093 } else {
3094 dc.col[f] = color.pixel;
3099 for (int f = 0; f < LEN(opt_colornames); ++f) if (opt_colornames[f]) free(opt_colornames[f]);
3103 static void xclear (int x1, int y1, int x2, int y2) {
3104 XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? term->deffg : term->defbg]);
3105 XFillRectangle(xw.dpy, term->picbuf, dc.gc, x1*xw.cw, y1*xw.ch, (x2-x1+1)*xw.cw, (y2-y1+1)*xw.ch);
3109 static void xhints (void) {
3110 XClassHint class = {opt_class, opt_title};
3111 XWMHints wm = {.flags = InputHint, .input = 1};
3112 XSizeHints size = {
3113 .flags = PSize | PResizeInc | PBaseSize,
3114 .height = xw.h,
3115 .width = xw.w,
3116 .height_inc = xw.ch,
3117 .width_inc = xw.cw,
3118 .base_height = 2*opt_border+xw.tabheight,
3119 .base_width = 2*opt_border,
3121 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, &size, &wm, &class);
3125 static XFontSet xinitfont (const char *fontstr) {
3126 XFontSet set;
3127 char *def, **missing;
3128 int n;
3130 missing = NULL;
3131 set = XCreateFontSet(xw.dpy, fontstr, &missing, &n, &def);
3132 if (missing) {
3133 while (n--) fprintf(stderr, "sterm: missing fontset: %s\n", missing[n]);
3134 XFreeStringList(missing);
3136 return set;
3140 static void xgetfontinfo (XFontSet set, int *ascent, int *descent, short *lbearing, short *rbearing) {
3141 XFontStruct **xfonts;
3142 char **font_names;
3143 int n;
3145 *ascent = *descent = *lbearing = *rbearing = 0;
3146 n = XFontsOfFontSet(set, &xfonts, &font_names);
3147 for (int f = 0; f < n; ++f) {
3148 *ascent = MAX(*ascent, (*xfonts)->ascent);
3149 *descent = MAX(*descent, (*xfonts)->descent);
3150 *lbearing = MAX(*lbearing, (*xfonts)->min_bounds.lbearing);
3151 *rbearing = MAX(*rbearing, (*xfonts)->max_bounds.rbearing);
3152 ++xfonts;
3157 static void initfonts (const char *fontstr, const char *bfontstr, const char *tabfont) {
3158 if ((dc.font[0].set = xinitfont(fontstr)) == NULL) die("can't load font %s", fontstr);
3159 xgetfontinfo(dc.font[0].set, &dc.font[0].ascent, &dc.font[0].descent, &dc.font[0].lbearing, &dc.font[0].rbearing);
3161 if ((dc.font[1].set = xinitfont(bfontstr)) == NULL) die("can't load font %s", bfontstr);
3162 xgetfontinfo(dc.font[1].set, &dc.font[1].ascent, &dc.font[1].descent, &dc.font[1].lbearing, &dc.font[1].rbearing);
3164 if ((dc.font[2].set = xinitfont(tabfont)) == NULL) die("can't load font %s", tabfont);
3165 xgetfontinfo(dc.font[2].set, &dc.font[2].ascent, &dc.font[2].descent, &dc.font[2].lbearing, &dc.font[2].rbearing);
3169 static void xinit (void) {
3170 XSetWindowAttributes attrs;
3171 Cursor cursor;
3172 Window parent;
3174 if (!(xw.dpy = XOpenDisplay(NULL))) die("can't open display");
3176 XA_VT_SELECTION = XInternAtom(xw.dpy, "_STERM_SELECTION_", 0);
3177 XA_CLIPBOARD = XInternAtom(xw.dpy, "CLIPBOARD", 0);
3178 XA_UTF8 = XInternAtom(xw.dpy, "UTF8_STRING", 0);
3179 XA_NETWM_NAME = XInternAtom(xw.dpy, "_NET_WM_NAME", 0);
3180 XA_TARGETS = XInternAtom(xw.dpy, "TARGETS", 0);
3182 xw.scr = XDefaultScreen(xw.dpy);
3183 /* font */
3184 initfonts(opt_fontnorm, opt_fontbold, opt_fonttab);
3185 /* XXX: Assuming same size for bold font */
3186 xw.cw = dc.font[0].rbearing-dc.font[0].lbearing;
3187 xw.ch = dc.font[0].ascent+dc.font[0].descent;
3188 xw.tch = dc.font[2].ascent+dc.font[2].descent;
3189 xw.tabheight = xw.tch+2;
3190 //fprintf(stderr, "tabh: %d\n", xw.tabheight);
3191 //xw.tabheight = 0;
3192 /* colors */
3193 xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
3194 xloadcols();
3195 /* window - default size */
3196 term->picbufh = term->row*xw.ch;
3197 term->picbufw = term->col*xw.cw;
3199 xw.h = term->picbufh+2*opt_border+xw.tabheight;
3200 xw.w = term->picbufw+2*opt_border;
3202 attrs.background_pixel = dc.col[defaultBG];
3203 attrs.border_pixel = dc.col[defaultBG];
3204 attrs.bit_gravity = NorthWestGravity;
3205 attrs.event_mask = FocusChangeMask | KeyPressMask
3206 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
3207 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask
3208 | EnterWindowMask | LeaveWindowMask;
3209 attrs.colormap = xw.cmap;
3210 //fprintf(stderr, "oe: [%s]\n", opt_embed);
3211 parent = opt_embed ? strtol(opt_embed, NULL, 0) : XRootWindow(xw.dpy, xw.scr);
3212 xw.win = XCreateWindow(xw.dpy, parent, 0, 0,
3213 xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
3214 XDefaultVisual(xw.dpy, xw.scr),
3215 CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask
3216 | CWColormap,
3217 &attrs);
3218 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3219 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
3220 /* input methods */
3221 xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
3222 xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL);
3223 /* gc */
3224 dc.gc = XCreateGC(xw.dpy, xw.win, 0, NULL);
3225 /* white cursor, black outline */
3226 cursor = XCreateFontCursor(xw.dpy, XC_xterm);
3227 XDefineCursor(xw.dpy, xw.win, cursor);
3228 XRecolorCursor(xw.dpy, cursor,
3229 &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff},
3230 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
3231 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
3232 fixWindowTitle(term);
3233 //XStoreName(xw.dpy, xw.win, opt_title);
3235 XSetForeground(xw.dpy, dc.gc, 0);
3236 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
3237 if (xw.tabheight > 0) XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
3239 XMapWindow(xw.dpy, xw.win);
3240 xhints();
3241 XSync(xw.dpy, 0);
3245 static void xdraws (const char *s, const Glyph *base, int x, int y, int charlen, int bytelen) {
3246 int fg = base->fg, bg = base->bg, temp;
3247 int winx = x*xw.cw, winy = y*xw.ch+dc.font[0].ascent, width = charlen*xw.cw;
3248 XFontSet fontset = dc.font[0].set;
3249 int defF = base->mode&ATTR_DEFFG, defB = base->mode&ATTR_DEFBG;
3251 /* only switch default fg/bg if term is in RV mode */
3252 if (IS_SET(MODE_REVERSE)) {
3253 if (defF) fg = term->defbg;
3254 if (defB) bg = term->deffg;
3256 if (base->mode&ATTR_REVERSE) defF = defB = 0;
3257 if (base->mode & ATTR_BOLD) {
3258 if (defF && defB && defaultBoldFG >= 0) fg = defaultBoldFG;
3259 else if (fg < 8) fg += 8;
3260 fontset = dc.font[1].set;
3262 if (base->mode & ATTR_UNDERLINE && defaultUnderlineFG >= 0) {
3263 if (defF && defB) fg = defaultUnderlineFG;
3266 if (base->mode&ATTR_REVERSE) { temp = fg; fg = bg; bg = temp; }
3268 XSetBackground(xw.dpy, dc.gc, dc.col[bg]);
3269 XSetForeground(xw.dpy, dc.gc, dc.col[fg]);
3271 if (IS_GFX(base->mode)) {
3272 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
3273 } else if (!needConversion) {
3274 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
3275 } else {
3276 if (bytelen > 0) {
3277 //k8: dunno why, but Xutf8DrawImageString() ignores ascii (at least for terminus)
3278 const char *pos = s;
3279 int xpos = winx;
3281 while (pos < s+bytelen) {
3282 const char *e;
3283 int clen;
3285 if ((unsigned char)(pos[0]) < 128) {
3286 for (e = pos+1; e < s+bytelen && (unsigned char)(*e) < 128; ++e) ;
3287 clen = e-pos;
3288 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
3289 } else {
3290 for (clen = 0, e = pos; e < s+bytelen && (unsigned char)(*e) >= 128; ++e) {
3291 if (((unsigned char)(e[0])&0xc0) == 0xc0) ++clen;
3293 Xutf8DrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
3295 xpos += xw.cw*clen;
3296 pos = e;
3301 if (base->mode & ATTR_UNDERLINE) {
3302 XDrawLine(xw.dpy, term->picbuf, dc.gc, winx, winy+1, winx+width-1, winy+1);
3307 /* copy buffer pixmap to screen pixmap */
3308 static void xcopy (int x, int y, int cols, int rows) {
3309 int src_x = x*xw.cw, src_y = y*xw.ch, src_w = cols*xw.cw, src_h = rows*xw.ch;
3310 int dst_x = opt_border+src_x, dst_y = opt_border+src_y;
3312 XCopyArea(xw.dpy, term->picbuf, xw.win, dc.gc, src_x, src_y, src_w, src_h, dst_x, dst_y);
3316 static void xdrawcursor (void) {
3317 int sl;
3318 int scrx, scry;
3320 if (term == NULL) return;
3322 LIMIT(term->oldcx, 0, term->col-1);
3323 LIMIT(term->oldcy, 0, term->row-1);
3325 scrx = term->oldcx;
3326 scry = term->oldcy+term->topline;
3327 if (scry < term->row &&
3328 (term->oldcy != term->c.y || term->oldcx != term->c.x || (term->c.state&CURSOR_HIDE) || !(xw.state & WIN_FOCUSED))) {
3329 /* remove the old cursor */
3330 sl = utf8size(term->line[term->oldcy][scrx].c);
3331 xdraws(term->line[term->oldcy][scrx].c, &term->line[term->oldcy][scrx], scrx, scry, 1, sl);
3332 //xclear(scrx, term->oldcy, scrx, term->oldcy);
3333 xcopy(scrx, scry, 1, 1);
3335 /* draw the new one */
3336 if (!(term->c.state&CURSOR_HIDE)) {
3337 Glyph g;
3339 scrx = term->c.x;
3340 scry = term->c.y+term->topline;
3341 if (scry < term->row) {
3342 if (!(xw.state & WIN_FOCUSED)) {
3343 if (defaultCursorInactiveBG < 0) {
3344 XSetForeground(xw.dpy, dc.gc, dc.col[defaultCursorBG]);
3345 XDrawRectangle(xw.dpy, term->picbuf, dc.gc, scrx*xw.cw, scry*xw.ch, xw.cw-1, xw.ch-1);
3346 goto done;
3348 g.bg = defaultCursorInactiveBG;
3349 g.fg = defaultCursorInactiveFG;
3350 } else {
3351 g.fg = defaultCursorFG;
3352 g.bg = defaultCursorBG;
3354 memcpy(g.c, term->line[term->c.y][scrx].c, UTF_SIZ);
3355 g.state = 0;
3356 g.mode = 0;
3357 if (IS_SET(MODE_REVERSE)) g.mode |= ATTR_REVERSE;
3358 sl = utf8size(g.c);
3359 xdraws(g.c, &g, scrx, scry, 1, sl);
3360 term->oldcx = scrx;
3361 term->oldcy = term->c.y;
3363 done:
3364 xcopy(scrx, scry, 1, 1);
3369 #define TAB_VISIBLE (6)
3371 static void xdrawTabBar (void) {
3372 if (xw.tabheight > 0 && updateTabBar) {
3373 static int tableft = 0;
3374 int tabstart;
3375 int tabw = xw.w/TAB_VISIBLE;
3376 XFontSet fontset = dc.font[2].set;
3378 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabBG]);
3379 XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
3381 if (tableft+TAB_VISIBLE-1 > termidx) tableft = termidx-TAB_VISIBLE;
3382 else if (termidx < tableft) tableft = termidx;
3383 if (tableft < 0) tableft = 0;
3384 tabstart = tableft;
3385 for (int f = tabstart; f < TAB_VISIBLE; ++f) {
3386 int x = (f-tabstart)*tabw;;
3387 const char *title;
3388 char *tit;
3390 if (f >= term_count) {
3391 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabBG]);
3392 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, xw.w, xw.tabheight);
3393 break;
3395 title = term_array[f]->title;
3396 if (!title[0]) title = opt_title;
3397 tit = SPrintf("[%d]%s", f, title);
3398 title = tit;
3400 XSetForeground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabBG : normalTabBG]);
3401 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, tabw, xw.tabheight);
3403 XSetBackground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabBG : normalTabBG]);
3404 XSetForeground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabFG : normalTabFG]);
3406 if (needConversion) {
3407 int xx = x+2;
3409 while (*title && xx < x+tabw) {
3410 const char *e = title;
3411 XRectangle r;
3413 memset(&r, 0, sizeof(r));
3415 if ((unsigned char)(*e) > 127) {
3416 while (*e && (unsigned char)(*e) > 127) ++e;
3417 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
3418 Xutf8TextExtents(fontset, title, e-title, &r, NULL);
3419 } else {
3420 while (*e && (unsigned char)(*e) <= 127) ++e;
3421 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
3422 XmbTextExtents(fontset, title, e-title, &r, NULL);
3424 title = e;
3425 xx += r.width-r.x;
3428 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
3429 } else {
3430 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
3432 free(tit);
3434 XSetForeground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabBG : normalTabBG]);
3435 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x+tabw-2, 0, 2, xw.tabheight);
3437 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabFG]);
3438 XDrawLine(xw.dpy, xw.pictab, dc.gc, x+tabw-1, 0, x+tabw-1, xw.tabheight);
3441 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabFG]);
3442 //XDrawRectangle(xw.dpy, xw.pictab, dc.gc, -1, 0, xw.w+1, xw.tabheight+1);
3443 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, 0);
3445 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, xw.h-xw.tabheight);
3446 updateTabBar = 0;
3450 static void drawline (int x1, int x2, int scry, int lineno) {
3451 int ic, ib, ox, sl;
3452 int stx, ex;
3453 Glyph base, new;
3455 if (scry < 0 || scry >= term->row) return;
3456 if (lineno < 0 || lineno >= term->linecount) {
3457 xclear(0, scry, term->col-1, scry);
3458 xcopy(0, scry, term->col, 1);
3459 } else {
3460 if (lineno < term->row && term->topline == 0) {
3461 //if (term->topline != 0) term->dirty[lineno] = 2;
3462 if (!term->dirty[lineno]) return;
3463 // fix 'dirty' flag for line
3464 if (term->dirty[lineno]&0x02) {
3465 // mark full line as dirty
3466 for (int x = 0; x < term->col; ++x) term->line[lineno][x].state |= GLYPH_DIRTY;
3468 // correct 'dirty' flag
3469 term->dirty[lineno] = 0;
3470 if (x1 > 0) for (int x = 0; x < x1; ++x) if (term->line[lineno][x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
3471 if (!term->dirty[lineno] && x2 < term->col) for (int x = x2; x < term->col; ++x) if (term->line[lineno][x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
3473 // find dirty region
3474 for (stx = x1; stx < x2; ++stx) if (term->line[lineno][stx].state&GLYPH_DIRTY) break;
3475 for (ex = x2; ex > stx; --ex) if (term->line[lineno][ex-1].state&GLYPH_DIRTY) break;
3476 if (stx >= x2 || ex <= stx) return; // nothing to do
3477 } else {
3478 stx = 0;
3479 ex = term->col;
3482 base = term->line[lineno][stx];
3483 ic = ib = 0;
3484 ox = stx;
3485 //xclear(stx, scry, ex-1, scry); //k8: actually, we don't need this for good monospace fonts, so...
3486 for (int x = stx; x < ex; ++x) {
3487 new = term->line[lineno][x];
3488 term->line[lineno][x].state &= ~GLYPH_DIRTY; //!
3489 if (term->sel.bx != -1 && new.c[0] && selected(x, lineno)) new.mode ^= ATTR_REVERSE;
3490 if (ib > 0 && (ATTRCMP(base, new) || ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
3491 // flush draw buffer
3492 xdraws(term->drawbuf, &base, ox, scry, ic, ib);
3493 ic = ib = 0;
3495 if (ib == 0) { ox = x; base = new; }
3496 sl = utf8size(new.c);
3497 memcpy(term->drawbuf+ib, new.c, sl);
3498 ib += sl;
3499 ++ic;
3501 if (ib > 0) xdraws(term->drawbuf, &base, ox, scry, ic, ib);
3502 //xcopy(0, scry, term->col, 1);
3503 if (term->c.y == lineno && term->c.x >= stx && term->c.x < ex) xdrawcursor();
3504 xcopy(stx, scry, ex-stx, 1);
3509 static void drawregion (int x1, int y1, int x2, int y2, int forced) {
3510 if (!forced && !(xw.state & WIN_VISIBLE)) return;
3512 if (term->topline < term->row) {
3513 for (int y = y1; y < y2; ++y) drawline(x1, x2, y+term->topline, y);
3515 if (term->topline > 0) {
3516 int scry = MIN(term->topline, term->row), y = term->row;
3518 if (term->topline >= term->row) y += term->topline-term->row;
3519 while (--scry >= 0) {
3520 drawline(0, term->col, scry, y);
3521 ++y;
3524 xdrawcursor();
3525 xdrawTabBar();
3526 XFlush(xw.dpy);
3530 static void draw (int forced) {
3531 if (term != NULL) {
3532 drawregion(0, 0, term->col, term->row, forced);
3533 term->lastDrawTime = mclock_ticks();
3538 static void expose (XEvent *ev) {
3539 XExposeEvent *e = &ev->xexpose;
3541 if (xw.state&WIN_REDRAW) {
3542 if (!e->count && term != NULL) {
3543 xw.state &= ~WIN_REDRAW;
3544 xcopy(0, 0, term->col, term->row);
3546 } else if (term != NULL) {
3547 XCopyArea(xw.dpy, term->picbuf, xw.win, dc.gc, e->x-opt_border, e->y-opt_border, e->width, e->height, e->x, e->y);
3549 xdrawTabBar();
3550 //XFlush(xw.dpy);
3554 static void visibility (XEvent *ev) {
3555 XVisibilityEvent *e = &ev->xvisibility;
3557 if (e->state == VisibilityFullyObscured) xw.state &= ~WIN_VISIBLE;
3558 else if ((xw.state&WIN_VISIBLE) == 0) xw.state |= WIN_VISIBLE | WIN_REDRAW; /* need a full redraw for next Expose, not just a buf copy */
3562 static void unmap (XEvent *ev) {
3563 xw.state &= ~WIN_VISIBLE;
3567 static void xseturgency (int add) {
3568 XWMHints *h = XGetWMHints(xw.dpy, xw.win);
3570 h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
3571 XSetWMHints(xw.dpy, xw.win, h);
3572 XFree(h);
3576 static void focus (XEvent *ev) {
3577 if (ev->type == FocusIn) {
3578 xw.state |= WIN_FOCUSED;
3579 xseturgency(0);
3580 } else {
3581 xw.state &= ~WIN_FOCUSED;
3583 //draw(1);
3584 xdrawcursor();
3585 xdrawTabBar();
3586 xcopy(0, 0, term->col, term->row);
3590 ////////////////////////////////////////////////////////////////////////////////
3591 // keyboard mapping
3592 static const char *kmap (KeySym k, uint state) {
3593 const char *res = NULL;
3594 state &= ~Mod2Mask; // numlock
3595 for (int f = 0; f < keymap_used; ++f) {
3596 uint mask = keymap[f].mask;
3598 if (keymap[f].key == k && ((state&mask) == mask || (mask == XK_NO_MOD && !state))) {
3599 //fprintf(stderr, "kp: %d\n", IS_SET(MODE_APPKEYPAD));
3600 if (!IS_SET(MODE_APPKEYPAD)) {
3601 if (!keymap[f].kp) {
3602 //fprintf(stderr, "nokp! %d %s\n", keymap[f].kp, keymap[f].str+1);
3603 return keymap[f].str; // non-keypad hit
3605 continue;
3607 //fprintf(stderr, "kp! %d %s\n", keymap[f].kp, keymap[f].str+1);
3608 if (keymap[f].kp) return keymap[f].str; // keypad hit
3609 res = keymap[f].str; // kp mode, but non-kp mapping found
3612 return res;
3616 static const char *kbind (KeySym k, uint state) {
3617 state &= ~Mod2Mask; // numlock
3618 for (int f = 0; f < keybinds_used; ++f) {
3619 uint mask = keybinds[f].mask;
3621 if (keybinds[f].key == k && ((state&mask) == mask || (mask == XK_NO_MOD && !state))) return keybinds[f].str;
3623 return NULL;
3627 static KeySym do_keytrans (KeySym ks) {
3628 for (int f = 0; f < keytrans_used; ++f) if (keytrans[f].src == ks) return keytrans[f].dst;
3629 return ks;
3633 static void kpress (XEvent *ev) {
3634 XKeyEvent *e = &ev->xkey;
3635 KeySym ksym = NoSymbol;
3636 const char *kstr;
3637 int len;
3638 Status status;
3639 char buf[32];
3641 if (term == NULL) return;
3642 //if (len < 1) len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
3643 if ((len = Xutf8LookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status)) > 0) buf[len] = 0;
3644 // leave only known mods
3645 e->state &= (Mod1Mask | Mod4Mask | ControlMask | ShiftMask);
3646 #ifdef DUMP_KEYSYMS
3648 const char *ksname = XKeysymToString(ksym);
3650 fprintf(stderr, "utf(%d):[%s] (%s) 0x%08x\n", len, len>=0?buf:"<shit>", ksname, (unsigned int)e->state);
3652 #endif
3653 if ((kstr = kbind(ksym, e->state)) != NULL) {
3654 // keybind found
3655 executeCommands(kstr);
3656 return;
3659 if ((kstr = kmap(do_keytrans(ksym), e->state)) != NULL) {
3660 if (kstr[0]) ttywritestr(kstr);
3661 } else {
3662 int meta = (e->state&Mod1Mask);
3664 int shift = (e->state&ShiftMask);
3665 int ctrl = (e->state&ControlMask);
3667 switch (ksym) {
3668 case XK_Return:
3669 if (meta) {
3670 ttywritestr("\x1b\x0a");
3671 } else {
3672 if (IS_SET(MODE_CRLF)) ttywritestr("\r\n"); else ttywritestr("\r");
3674 break;
3675 default:
3676 if (len > 0) {
3677 if (meta && len == 1) ttywritestr("\x1b");
3678 ttywrite(buf, len);
3680 break;
3686 ////////////////////////////////////////////////////////////////////////////////
3687 // xembed?
3688 static void cmessage (XEvent *e) {
3689 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
3690 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
3691 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
3692 xw.state |= WIN_FOCUSED;
3693 xseturgency(0);
3694 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
3695 xw.state &= ~WIN_FOCUSED;
3697 draw(1);
3702 ////////////////////////////////////////////////////////////////////////////////
3703 static void resize (XEvent *e) {
3704 int col, row;
3705 Term *ot = term;
3707 if (e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) return;
3708 xw.w = e->xconfigure.width;
3709 xw.h = e->xconfigure.height;
3710 col = (xw.w-2*opt_border)/xw.cw;
3711 row = (xw.h-2*opt_border-xw.tabheight)/xw.ch;
3712 if (col == term->col && row == term->row) return;
3713 for (int f = 0; f < term_count; ++f) {
3714 term = term_array[f];
3715 if (tresize(col, row) && ot == term) draw(1);
3716 ttyresize();
3717 xresize(col, row);
3719 term = ot;
3720 XFreePixmap(xw.dpy, xw.pictab);
3721 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
3722 updateTabBar = 1;
3726 static inline int last_draw_too_old (void) {
3727 return (mclock_ticks()-term->lastDrawTime >= DRAW_TIMEOUT);
3731 ////////////////////////////////////////////////////////////////////////////////
3732 // main loop
3733 static void (*handler[LASTEvent])(XEvent *) = {
3734 [KeyPress] = kpress,
3735 [ClientMessage] = cmessage,
3736 [ConfigureNotify] = resize,
3737 [VisibilityNotify] = visibility,
3738 [UnmapNotify] = unmap,
3739 [Expose] = expose,
3740 [FocusIn] = focus,
3741 [FocusOut] = focus,
3742 [MotionNotify] = bmotion,
3743 [ButtonPress] = bpress,
3744 [ButtonRelease] = brelease,
3745 [SelectionNotify] = selnotify,
3746 [SelectionRequest] = selrequest,
3747 [SelectionClear] = selclear,
3751 static void run (void) {
3752 //int stuff_to_print = 0;
3753 int xfd = XConnectionNumber(xw.dpy);
3755 while (term_count > 0) {
3756 XEvent ev;
3757 fd_set rfd, wfd;
3758 struct timeval timeout;
3759 int maxfd = xfd;
3761 FD_ZERO(&rfd);
3762 FD_ZERO(&wfd);
3763 FD_SET(xfd, &rfd);
3764 //FD_SET(term->cmdfd, &rfd);
3765 // have something to write?
3766 for (int f = 0; f < term_count; ++f) {
3767 Term *t = term_array[f];
3769 if (!t->dead && term->cmdfd >= 0 && t->pid != 0) {
3770 if (t->cmdfd > maxfd) maxfd = t->cmdfd;
3771 FD_SET(t->cmdfd, &rfd);
3772 if (t->wrbufpos < t->wrbufused) FD_SET(t->cmdfd, &wfd);
3776 timeout.tv_sec = 0;
3777 timeout.tv_usec = SELECT_TIMEOUT*1000;
3778 if (select(maxfd+1, &rfd, &wfd, NULL, &timeout) < 0) {
3779 if (errno == EINTR) continue;
3780 die("select failed: %s", SERRNO);
3783 for (int f = 0; f < term_count; ++f) {
3784 Term *t = term_array[f];
3786 if (!t->dead && term->cmdfd >= 0) {
3787 if (FD_ISSET(t->cmdfd, &wfd)) {
3788 Term *o = term;
3790 term = t;
3791 ttyflushwrbuf();
3792 term = o;
3794 if (FD_ISSET(t->cmdfd, &rfd)) {
3795 Term *o = term;
3797 term = t;
3798 ttyread();
3799 term = o;
3800 //t->wantRedraw = 1;
3801 //if (t == term) stuff_to_print = 1;
3806 termcleanup();
3807 if (term_count == 0) break;
3809 if (updateTabBar || (term != NULL && !term->dead && term->wantRedraw && last_draw_too_old())) {
3810 //stuff_to_print = 0;
3811 term->wantRedraw = 0;
3812 draw(0);
3815 if (XPending(xw.dpy)) {
3816 while (XPending(xw.dpy)) {
3817 XNextEvent(xw.dpy, &ev);
3818 if (XFilterEvent(&ev, xw.win)) continue;
3819 if (handler[ev.type]) (handler[ev.type])(&ev);
3826 ////////////////////////////////////////////////////////////////////////////////
3827 typedef const char * (*IniHandlerFn) (const char *optname, const char *fmt, char *argstr, void *udata);
3830 typedef struct {
3831 const char *name;
3832 const char *fmt;
3833 void *udata;
3834 IniHandlerFn fn;
3835 } IniCommand;
3837 static const char *inifnGenericOneArg (const char *optname, const char *fmt, char *argstr, void *udata) {
3838 return iniParseArguments(argstr, fmt, udata);
3842 static const char *inifnGenericOneStr (const char *optname, const char *fmt, char *argstr, void *udata) {
3843 char *s = NULL;
3844 const char *err = iniParseArguments(argstr, fmt, &s);
3846 if (err != NULL) return err;
3847 if ((s = strdup(s)) == NULL) return "out of memory";
3848 if (udata) {
3849 char **ustr = (char **)udata;
3851 if (*ustr) free(*ustr);
3852 *ustr = s;
3854 return NULL;
3858 static const IniCommand iniCommands[] = {
3859 {"term", "s!-", &opt_term, inifnGenericOneStr},
3860 {"class", "s!-", &opt_class, inifnGenericOneStr},
3861 {"title", "s!-", &opt_title, inifnGenericOneStr},
3862 {"fontnorm", "s!-", &opt_fontnorm, inifnGenericOneStr},
3863 {"fontbold", "s!-", &opt_fontbold, inifnGenericOneStr},
3864 {"fonttab", "s!-", &opt_fonttab, inifnGenericOneStr},
3865 {"shell", "s!-", &opt_shell, inifnGenericOneStr},
3866 {"doubleclick_timeout", "i{0,10000}", &opt_doubleclick_timeout, inifnGenericOneArg},
3867 {"tripleclick_timeout", "i{0,10000}", &opt_tripleclick_timeout, inifnGenericOneArg},
3868 {"tabsize", "i{1,256}", &opt_tabsize, inifnGenericOneArg},
3869 {"border", "i{0,256}", &opt_border, inifnGenericOneArg},
3870 {"defaultfg", "i{0,511}", &defaultFG, inifnGenericOneArg},
3871 {"defaultbg", "i{0,511}", &defaultBG, inifnGenericOneArg},
3872 {"defaultcursorfg", "i{0,511}", &defaultCursorFG, inifnGenericOneArg},
3873 {"defaultcursorbg", "i{0,511}", &defaultCursorBG, inifnGenericOneArg},
3874 {"defaultinactivecursorfg", "i{0,511}", &defaultCursorInactiveFG, inifnGenericOneArg},
3875 {"defaultinactivecursorbg", "i{-1,511}", &defaultCursorInactiveBG, inifnGenericOneArg},
3876 {"defaultboldfg", "i{-1,511}", &defaultBoldFG, inifnGenericOneArg},
3877 {"defaultunderlinefg", "i{-1,511}", &defaultUnderlineFG, inifnGenericOneArg},
3878 {"normaltabfg", "i{0,511}", &normalTabFG, inifnGenericOneArg},
3879 {"normaltabbg", "i{0,511}", &normalTabBG, inifnGenericOneArg},
3880 {"activetabfg", "i{0,511}", &activeTabFG, inifnGenericOneArg},
3881 {"activetabbg", "i{0,511}", &activeTabBG, inifnGenericOneArg},
3882 {"maxhistory", "i{0,65535}", &opt_maxhistory, inifnGenericOneArg},
3883 {NULL, NULL, NULL, NULL}
3887 #define INI_LINE_SIZE (32768)
3889 // <0: file not found
3890 // >0: file loading error
3891 // 0: ok
3892 static int loadConfig (const char *fname) {
3893 int inifelse = 0; // 0: not; 1: doing true; 2: doing false; -1: waiting else/endif; -2: waiting endif
3894 FILE *fi = fopen(fname, "r");
3895 const char *err = NULL;
3896 char *line;
3897 int lineno = 0;
3899 if (fi == NULL) return -1;
3900 if ((line = malloc(INI_LINE_SIZE)) == NULL) { err = "out of memory"; goto quit; }
3902 while (fgets(line, INI_LINE_SIZE-1, fi) != NULL) {
3903 char *optname, *argstr;
3904 int goodoption = 0;
3906 ++lineno;
3907 line[INI_LINE_SIZE-1] = 0;
3908 // get option name
3909 for (optname = line; *optname && isspace(*optname); ++optname) ;
3910 if (!optname[0] || optname[0] == '#') continue; // comment
3911 if (!isalnum(optname[0])) { err = "invalid option name"; goto quit; }
3912 argstr = optname;
3913 while (*argstr) {
3914 if (!argstr[0] || isspace(argstr[0])) break;
3915 if (!isalnum(argstr[0]) && argstr[0] != '_' && argstr[0] != '.') { err = "invalid option name"; goto quit; }
3916 *argstr = tolower(*argstr);
3917 ++argstr;
3919 if (*argstr) *argstr++ = 0;
3921 if (strcasecmp(optname, "ifterm") == 0) {
3922 char *val;
3924 if (inifelse != 0) { err = "nested ifs are not allowed"; goto quit; }
3925 inifelse = -1;
3926 if ((err = iniParseArguments(argstr, "s", &val)) != NULL) goto quit;
3927 if (strcasecmp(opt_term, val) == 0) inifelse = 1;
3928 continue;
3930 if (strcasecmp(optname, "else") == 0) {
3931 switch (inifelse) {
3932 case -1: inifelse = 2; break;
3933 case 2: case -2: case 0: err = "else without if"; goto quit;
3934 case 1: inifelse = -2; break;
3936 continue;
3938 if (strcasecmp(optname, "endif") == 0) {
3939 switch (inifelse) {
3940 case -1: case -2: case 1: case 2: inifelse = 0; break;
3941 case 0: err = "endif without if"; goto quit;
3943 continue;
3946 if (inifelse < 0) {
3947 //trimstr(argstr);
3948 //fprintf(stderr, "skip: [%s]\n", argstr);
3949 continue;
3951 if (opt_term_locked && strcasecmp(optname, "term") == 0) continue; // termname given in command line
3952 // ok, we have option name in `optname` and arguments in `argstr`
3953 if (strncmp(optname, "color.", 6) == 0) {
3954 int n = 0;
3955 char *s = NULL;
3957 optname += 6;
3958 if (!optname[0]) { err = "invalid color option"; goto quit; }
3959 while (*optname) {
3960 if (!isdigit(*optname)) { err = "invalid color option"; goto quit; }
3961 n = (n*10)+(optname[0]-'0');
3962 ++optname;
3964 if (n < 0 || n > 511) { err = "invalid color index"; goto quit; }
3966 if ((err = iniParseArguments(argstr, "s!-", &s)) != NULL) goto quit;
3967 if ((s = strdup(s)) == NULL) { err = "out of memory"; goto quit; }
3968 if (opt_colornames[n] != NULL) free(opt_colornames[n]);
3969 opt_colornames[n] = s;
3970 continue;
3973 if (strcmp(optname, "unimap") == 0) {
3974 int uni, ch;
3975 char *alt = NULL;
3977 //unimap 0x2592 0x61 alt
3978 if ((err = iniParseArguments(argstr, "i{0,65535}i{0,126}|s!-", &uni, &ch, &alt)) != NULL) goto quit;
3979 if (alt != NULL && strcasecmp(alt, "alt") != 0) { err = "invalid unimap"; goto quit; }
3980 if (unimap == NULL) {
3981 if ((unimap = calloc(65536, sizeof(unimap[0]))) == NULL) { err = "out of memory"; goto quit; }
3983 if (alt != NULL && ch == 0) alt = NULL;
3984 if (alt != NULL && ch < 96) { err = "invalid unimap"; goto quit; }
3985 unimap[uni] = ch;
3986 if (alt != NULL) unimap[uni] |= 0x80;
3987 continue;
3990 if (strcmp(optname, "keytrans_reset") == 0) {
3991 if ((err = iniParseArguments(argstr, "")) != NULL) goto quit;
3992 keytrans_reset();
3993 continue;
3995 if (strcmp(optname, "keytrans") == 0) {
3996 char *src = NULL, *dst = NULL;
3998 if ((err = iniParseArguments(argstr, "s!-s!-", &src, &dst)) != NULL) goto quit;
3999 keytrans_add(src, dst);
4000 continue;
4003 if (strcmp(optname, "keybind_reset") == 0) {
4004 if ((err = iniParseArguments(argstr, "")) != NULL) goto quit;
4005 keybinds_reset();
4006 continue;
4008 if (strcmp(optname, "keybind") == 0) {
4009 char *key = NULL, *act = NULL;
4011 if ((err = iniParseArguments(argstr, "s!-R!", &key, &act)) != NULL) goto quit;
4012 keybind_add(key, act);
4013 continue;
4016 if (strcmp(optname, "keymap_reset") == 0) {
4017 if ((err = iniParseArguments(argstr, "")) != NULL) goto quit;
4018 keymap_reset();
4019 continue;
4021 if (strcmp(optname, "keymap") == 0) {
4022 char *key = NULL, *str = NULL;
4024 if ((err = iniParseArguments(argstr, "s!-s!-", &key, &str)) != NULL) goto quit;
4025 keymap_add(key, str);
4026 continue;
4029 for (int f = 0; iniCommands[f].name != NULL; ++f) {
4030 if (strcmp(iniCommands[f].name, optname) == 0) {
4031 if ((err = iniCommands[f].fn(optname, iniCommands[f].fmt, argstr, iniCommands[f].udata)) != NULL) goto quit;
4032 goodoption = 1;
4033 break;
4036 if (!goodoption) { err = "unknown option"; goto quit; }
4038 quit:
4039 if (line != NULL) free(line);
4040 fclose(fi);
4041 if (err == NULL && inifelse != 0) err = "if without endif";
4042 if (err != NULL) die("ini error at line %d: %s", lineno, err);
4043 return 0;
4047 static void initDefaultOptions (void) {
4048 opt_title = strdup("sterm");
4049 opt_class = strdup("sterm");
4050 opt_term = strdup(TNAME);
4051 opt_fontnorm = strdup(FONT);
4052 opt_fontbold = strdup(BOLDFONT);
4053 opt_fonttab = strdup(FONTTAB);
4054 opt_shell = strdup(SHELL);
4056 memset(opt_colornames, 0, sizeof(opt_colornames));
4057 for (int f = 0; f < LEN(defcolornames); ++f) opt_colornames[f] = strdup(defcolornames[f]);
4058 for (int f = 0; f < LEN(defextcolornames); ++f) opt_colornames[f+256] = strdup(defextcolornames[f]);
4060 keytrans_add("KP_Home", "Home");
4061 keytrans_add("KP_Left", "Left");
4062 keytrans_add("KP_Up", "Up");
4063 keytrans_add("KP_Right", "Right");
4064 keytrans_add("KP_Down", "Down");
4065 keytrans_add("KP_Prior", "Prior");
4066 keytrans_add("KP_Next", "Next");
4067 keytrans_add("KP_End", "End");
4068 keytrans_add("KP_Begin", "Begin");
4069 keytrans_add("KP_Insert", "Insert");
4070 keytrans_add("KP_Delete", "Delete");
4072 keybind_add("shift+Insert", "PastePrimary");
4073 keybind_add("alt+Insert", "PasteSecondary");
4075 keymap_add("BackSpace", "\177");
4076 keymap_add("Insert", "\x1b[2~");
4077 keymap_add("Delete", "\x1b[3~");
4078 keymap_add("Home", "\x1b[1~");
4079 keymap_add("End", "\x1b[4~");
4080 keymap_add("Prior", "\x1b[5~");
4081 keymap_add("Next", "\x1b[6~");
4082 keymap_add("F1", "\x1bOP");
4083 keymap_add("F2", "\x1bOQ");
4084 keymap_add("F3", "\x1bOR");
4085 keymap_add("F4", "\x1bOS");
4086 keymap_add("F5", "\x1b[15~");
4087 keymap_add("F6", "\x1b[17~");
4088 keymap_add("F7", "\x1b[18~");
4089 keymap_add("F8", "\x1b[19~");
4090 keymap_add("F9", "\x1b[20~");
4091 keymap_add("F10", "\x1b[21~");
4092 keymap_add("Up", "\x1bOA");
4093 keymap_add("Down", "\x1bOB");
4094 keymap_add("Right", "\x1bOC");
4095 keymap_add("Left", "\x1bOD");
4096 keymap_add("kpad+Up", "\x1bOA");
4097 keymap_add("kpad+Down", "\x1bOB");
4098 keymap_add("kpad+Right", "\x1bOC");
4099 keymap_add("kpad+Left", "\x1bOD");
4103 ////////////////////////////////////////////////////////////////////////////////
4104 static Term *oldTerm;
4105 static int oldTermIdx;
4106 static Term *newTerm;
4107 static int newTermIdx;
4108 static int newTermSwitch;
4111 typedef void (*CmdHandlerFn) (const char *cmdname, char *argstr);
4113 typedef struct {
4114 const char *name;
4115 CmdHandlerFn fn;
4116 } Command;
4119 static void cmdPastePrimary (const char *cmdname, char *argstr) {
4120 selpaste(XA_PRIMARY);
4124 static void cmdPasteSecondary (const char *cmdname, char *argstr) {
4125 selpaste(XA_SECONDARY);
4129 static void cmdPasteClipboard (const char *cmdname, char *argstr) {
4130 selpaste(XA_CLIPBOARD);
4134 static void cmdExec (const char *cmdname, char *argstr) {
4135 if (term->execcmd != NULL) free(term->execcmd);
4136 if (argstr[0]) {
4137 term->execcmd = strdup(argstr);
4138 } else {
4139 term->execcmd = NULL;
4144 static int parseTabArgs (char *argstr, int *noswitch, int nowrap, int idx) {
4145 for (;;) {
4146 char *arg;
4148 while (*argstr && isspace(*argstr)) ++argstr;
4149 if (!argstr[0]) break;
4150 if (iniParseArguments(argstr, "s-R-", &arg, &argstr) != NULL) break;
4152 if (strcasecmp(arg, "noswitch") == 0) *noswitch = 1;
4153 else if (strcasecmp(arg, "switch") == 0) *noswitch = 0;
4154 else if (strcasecmp(arg, "nowrap") == 0) nowrap = 1;
4155 else if (strcasecmp(arg, "wrap") == 0) nowrap = 0;
4156 else if (strcasecmp(arg, "first") == 0) idx = 0;
4157 else if (strcasecmp(arg, "last") == 0) idx = term_count-1;
4158 else if (strcasecmp(arg, "prev") == 0) idx = -1;
4159 else if (strcasecmp(arg, "next") == 0) idx = -2;
4160 else {
4161 long int n = -1;
4162 char *eptr;
4164 n = strtol(arg, &eptr, 0);
4165 if (!eptr[0] && n >= 0 && n < term_count) idx = n;
4168 switch (idx) {
4169 case -1: // prev
4170 if ((idx = termidx-1) < 0) idx = nowrap ? 0 : term_count-1;
4171 break;
4172 case -2: // next
4173 if ((idx = termidx+1) >= term_count) idx = nowrap ? term_count-1 : 0;
4174 break;
4176 return idx;
4180 static void flushNewTerm (void) {
4181 if (newTerm != NULL) {
4182 term = newTerm;
4183 termidx = newTermIdx;
4184 tinitialize(term_array[0]->col, term_array[0]->row);
4186 term->picbufh = term->row*xw.ch;
4187 term->picbufw = term->col*xw.cw;
4188 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
4189 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
4191 if (ttynew(term) != 0) {
4192 term = oldTerm;
4193 termidx = oldTermIdx;
4194 termfree(newTermIdx);
4195 } else {
4196 selinit();
4197 ttyresize();
4198 if (newTermSwitch) {
4199 term = NULL;
4200 termidx = 0;
4201 switchToTerm(newTermIdx, 1);
4202 oldTerm = term;
4203 oldTermIdx = termidx;
4204 } else {
4205 term = oldTerm;
4206 termidx = oldTermIdx;
4209 newTerm = NULL;
4214 static void cmdNewTab (const char *cmdname, char *argstr) {
4215 int noswitch = 0, idx;
4217 flushNewTerm();
4218 if ((newTerm = termalloc()) == NULL) return;
4219 /*idx =*/ parseTabArgs(argstr, &noswitch, 0, termidx);
4220 idx = term_count-1;
4221 if (!noswitch) oldTermIdx = idx;
4222 newTermIdx = termidx = idx;
4223 term = newTerm;
4224 newTermSwitch = !noswitch;
4228 static void cmdCloseTab (const char *cmdname, char *argstr) {
4229 flushNewTerm();
4230 if (!term->dead) kill(term->pid, SIGTERM);
4234 static void cmdKillTab (const char *cmdname, char *argstr) {
4235 flushNewTerm();
4236 if (!term->dead) kill(term->pid, SIGKILL);
4240 static void cmdSwitchToTab (const char *cmdname, char *argstr) {
4241 int noswitch = 0;
4243 flushNewTerm();
4244 switchToTerm(parseTabArgs(argstr, &noswitch, 0, -2), 1);
4245 oldTerm = term;
4246 oldTermIdx = termidx;
4250 static void cmdMoveTabTo (const char *cmdname, char *argstr) {
4251 int noswitch = 0, idx;
4253 flushNewTerm();
4254 idx = parseTabArgs(argstr, &noswitch, 0, termidx);
4255 if (idx != termidx && idx >= 0 && idx < term_count) {
4256 Term *t = term_array[termidx];
4258 // remove current term
4259 for (int f = termidx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
4260 // insert term
4261 for (int f = term_count-2; f >= idx; --f) term_array[f+1] = term_array[f];
4262 term_array[idx] = t;
4263 termidx = idx;
4264 oldTerm = t;
4265 oldTermIdx = idx;
4266 updateTabBar = 1;
4271 static void cmdDefaultFG (const char *cmdname, char *argstr) {
4272 int c;
4274 if (iniParseArguments(argstr, "i{0,511}", &c) == NULL) term->deffg = c;
4278 static void cmdDefaultBG (const char *cmdname, char *argstr) {
4279 int c;
4281 if (iniParseArguments(argstr, "i{0,511}", &c) == NULL) term->defbg = c;
4285 static void scrollHistory (int delta) {
4286 if (term->maxhistory < 1) return; // no history
4287 term->topline += delta;
4288 if (term->topline > term->maxhistory-term->row) term->topline = term->maxhistory-term->row;
4289 if (term->topline < 0) term->topline = 0;
4290 tfulldirt();
4291 draw(1);
4295 static void cmdScrollHistoryLineUp (const char *cmdname, char *argstr) {
4296 scrollHistory(1);
4300 static void cmdScrollHistoryPageUp (const char *cmdname, char *argstr) {
4301 scrollHistory(term->row);
4305 static void cmdScrollHistoryLineDown (const char *cmdname, char *argstr) {
4306 scrollHistory(-1);
4310 static void cmdScrollHistoryPageDown (const char *cmdname, char *argstr) {
4311 scrollHistory(-term->row);
4315 static void cmdScrollHistoryTop (const char *cmdname, char *argstr) {
4316 scrollHistory(term->linecount);
4320 static void cmdScrollHistoryBottom (const char *cmdname, char *argstr) {
4321 scrollHistory(-term->linecount);
4325 static const Command commandList[] = {
4326 {"PastePrimary", cmdPastePrimary},
4327 {"PasteSecondary", cmdPasteSecondary},
4328 {"PasteClipboard", cmdPasteClipboard},
4329 {"exec", cmdExec},
4330 {"NewTab", cmdNewTab}, // 'noswitch' 'next' 'prev' 'first' 'last'
4331 {"CloseTab", cmdCloseTab},
4332 {"KillTab", cmdKillTab},
4333 {"SwitchToTab", cmdSwitchToTab}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
4334 {"MoveTabTo", cmdMoveTabTo}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
4335 {"defaultfg", cmdDefaultFG},
4336 {"defaultbg", cmdDefaultBG},
4337 {"ScrollHistoryLineUp", cmdScrollHistoryLineUp},
4338 {"ScrollHistoryPageUp", cmdScrollHistoryPageUp},
4339 {"ScrollHistoryLineDown", cmdScrollHistoryLineDown},
4340 {"ScrollHistoryPageDown", cmdScrollHistoryPageDown},
4341 {"ScrollHistoryTop", cmdScrollHistoryTop},
4342 {"ScrollHistoryBottom", cmdScrollHistoryBottom},
4344 {"term", cmdTermName},
4345 {"title", cmdWinTitle},
4346 {"tabsize", cmdTabSize},
4347 {"defaultcursorfg", cmdDefaultCursorFG},
4348 {"defaultcursorbg", cmdDefaultCursorBG},
4349 {"defaultinactivecursorfg", cmdDefaultInactiveCursorFG},
4350 {"defaultinactivecursorbg", cmdDefaultInactiveCursorBG},
4351 {"defaultboldfg", cmdDefaultBoldFG},
4352 {"defaultunderlinefg", cmdDefaultUnderlineFG},
4354 {NULL, NULL}
4358 static void executeCommand (const char *str, int slen) {
4359 const char *e;
4360 char *cmdname;
4362 if (str == NULL) return;
4363 if (slen < 0) slen = strlen(str);
4365 for (int f = 0; f < slen; ++f) if (!str[f]) { slen = f; break; }
4367 while (slen > 0 && isspace(*str)) { ++str; --slen; }
4368 if (slen < 1 || !str[0]) return;
4370 for (e = str; slen > 0 && !isspace(*e); ++e, --slen) ;
4372 if (e-str > 127) return;
4373 cmdname = alloca(e-str+8);
4374 if (cmdname == NULL) return;
4375 memcpy(cmdname, str, e-str);
4376 cmdname[e-str] = 0;
4377 while (slen > 0 && isspace(*e)) { ++e; --slen; }
4379 for (int f = 0; commandList[f].name != NULL; ++f) {
4380 if (strcasecmp(commandList[f].name, cmdname) == 0) {
4381 char *left = calloc(slen+2, 1);
4383 if (left != NULL) {
4384 if (slen > 0) memcpy(left, e, slen);
4385 //fprintf(stderr, "command: [%s]; args: [%s]\n", cmdname, left);
4386 commandList[f].fn(cmdname, left);
4387 free(left);
4389 break;
4395 static void executeCommands (const char *str) {
4396 oldTerm = term;
4397 oldTermIdx = termidx;
4398 newTerm = NULL;
4399 newTermSwitch = 0;
4400 if (str == NULL) return;
4401 while (*str) {
4402 const char *ce;
4403 char qch;
4405 while (*str && isspace(*str)) ++str;
4406 if (!*str) break;
4407 if (*str == ';') { ++str; continue; }
4409 ce = str;
4410 qch = ' ';
4411 while (*ce) {
4412 if (*ce == ';' && qch == ' ') break;
4413 if (qch != ' ' && *ce == qch) { qch = ' '; ++ce; continue; }
4414 if (*ce == '"' || *ce == '\'') {
4415 if (qch == ' ') qch = *ce;
4416 ++ce;
4417 continue;
4419 if (*ce++ == '\\' && *ce) ++ce;
4422 executeCommand(str, ce-str);
4423 if (*ce) str = ce+1; else break;
4425 flushNewTerm();
4426 switchToTerm(oldTermIdx, 1);
4430 ////////////////////////////////////////////////////////////////////////////////
4431 int main (int argc, char *argv[]) {
4432 char *configfile = NULL;
4434 //dbgLogInit();
4436 for (int f = 1; f < argc; f++) {
4437 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
4438 if (strcmp(argv[f], "-into") == 0) { ++f; continue; }
4439 if (strcmp(argv[f], "-embed") == 0) { ++f; continue; }
4440 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
4441 case 'e': f = argc+1; break;
4442 case 't':
4443 case 'c':
4444 case 'w':
4445 case 'b':
4446 ++f;
4447 break;
4448 case 'T':
4449 if (++f < argc) {
4450 free(opt_term);
4451 opt_term = strdup(argv[f]);
4452 opt_term_locked = 1;
4454 break;
4455 case 'C':
4456 if (++f < argc) {
4457 if (configfile) free(configfile);
4458 configfile = strdup(argv[f]);
4460 break;
4461 case 'v':
4462 case 'h':
4463 default:
4464 fprintf(stderr, "%s", USAGE);
4465 exit(EXIT_FAILURE);
4469 initDefaultOptions();
4470 if (configfile == NULL) {
4471 const char *home = getenv("HOME");
4473 if (home != NULL) {
4474 configfile = SPrintf("%s/.sterm.rc", home);
4475 if (loadConfig(configfile) == 0) goto cfgdone;
4476 free(configfile); configfile = NULL;
4478 configfile = SPrintf("%s/.config/sterm.rc", home);
4479 if (loadConfig(configfile) == 0) goto cfgdone;
4480 free(configfile); configfile = NULL;
4483 configfile = SPrintf("/etc/sterm.rc");
4484 if (loadConfig(configfile) == 0) goto cfgdone;
4485 free(configfile); configfile = NULL;
4487 configfile = SPrintf("/etc/sterm/sterm.rc");
4488 if (loadConfig(configfile) == 0) goto cfgdone;
4489 free(configfile); configfile = NULL;
4491 configfile = SPrintf("./.sterm.rc");
4492 if (loadConfig(configfile) == 0) goto cfgdone;
4493 free(configfile); configfile = NULL;
4494 // no config
4495 } else {
4496 if (loadConfig(configfile) < 0) die("config file '%s' not found!", configfile);
4498 cfgdone:
4499 if (configfile != NULL) free(configfile);
4501 for (int f = 1; f < argc; f++) {
4502 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
4503 if (strcmp(argv[f], "-into") == 0 || strcmp(argv[f], "-embed") == 0) {
4504 if (opt_embed) free(opt_embed);
4505 opt_embed = strdup(argv[f]);
4506 continue;
4508 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
4509 case 't':
4510 if (++f < argc) {
4511 free(opt_title);
4512 opt_title = strdup(argv[f]);
4514 break;
4515 case 'c':
4516 if (++f < argc) {
4517 free(opt_class);
4518 opt_class = strdup(argv[f]);
4520 break;
4521 case 'w':
4522 if (++f < argc) {
4523 if (opt_embed) free(opt_embed);
4524 opt_embed = strdup(argv[f]);
4526 break;
4527 case 'e':
4528 /* eat every remaining arguments */
4529 if (++f < argc) opt_cmd = &argv[f];
4530 f = argc+1;
4531 case 'T':
4532 ++f;
4533 break;
4534 case 'C':
4535 if (++f < argc) {
4536 if (configfile) free(configfile);
4537 configfile = strdup(argv[f]);
4539 break;
4540 case 'v':
4541 case 'h':
4542 default:
4543 fprintf(stderr, "%s", USAGE);
4544 exit(EXIT_FAILURE);
4548 setenv("TERM", opt_term, 1);
4549 mclock_init();
4550 setlocale(LC_ALL, "");
4551 initLCConversion();
4552 updateTabBar = 1;
4553 termidx = 0;
4554 term = termalloc();
4555 if (term->execcmd != NULL) { free(term->execcmd); term->execcmd = NULL; }
4556 tinitialize(80, 25);
4557 if (ttynew(term) != 0) die("can't run process");
4558 opt_cmd = NULL;
4559 xinit();
4560 selinit();
4561 run();
4562 return 0;