fixed bug with redrawing
[k8sterm.git] / src / sterm.c
bloba6231551eb2e37b84bc1c0200525f3a55e7d45ad
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>
37 //#include "dbglog.h"
40 //#define DUMP_KEYSYMS
42 //#define KEYPAD_DUMP
44 //#define DUMP_PROG_OUTPUT
45 //#define DUMP_PROG_INPUT
47 //#define DUMP_IO_READ
48 //#define DUMP_IO_WRITE
50 #if defined(DUMP_IO_READ) || defined(DUMP_IO_WRITE)
51 # define DUMP_IO
52 #endif
54 #ifdef KEYPAD_DUMP
55 # define DUMP_KEYPAD_SWITCH(strflag,strstate) do { fprintf(stderr, "KEYPAD %s (%s)\n", (strstate), (strflag)); } while (0)
56 #else
57 # define DUMP_KEYPAD_SWITCH(strflag,strstate) ((void)(sizeof(strflag)+sizeof(strstate)))
58 #endif
61 ////////////////////////////////////////////////////////////////////////////////
62 #define USAGE \
63 "sterm " VERSION " (c) 2010-2012 st engineers and Ketmar // Vampire Avalon\n" \
64 "usage: sterm [-t title] [-c class] [-w windowid] [-T termname] [-C config_file] [-v] [-e command...]\n"
67 ////////////////////////////////////////////////////////////////////////////////
68 #define FONT "-*-liberation mono-medium-*-*-*-17-*-*-*-*-*-koi8-u"
69 #define BOLDFONT "-*-liberation mono-medium-*-*-*-17-*-*-*-*-*-koi8-u"
70 #define FONTTAB "-*-helvetica-*-*-*-*-12-*-*-*-*-*-*-*"
72 #define FONT "-*-terminus-bold-*-*-*-20-*-*-*-*-*-koi8-u"
73 #define BOLDFONT "-*-terminus-bold-*-*-*-20-*-*-*-*-*-koi8-u"
77 /* Space in pixels around the terminal buffer */
78 #define BORDER (2)
81 /* Default shell to use if SHELL is not set in the env */
82 #define SHELL "/bin/sh"
85 /* Terminal colors (16 first used in escape sequence) */
86 static const char *defcolornames[] = {
87 #if 1
88 /* 8 normal colors */
89 "black",
90 "red3",
91 "green3",
92 "yellow3",
93 "blue2",
94 "magenta3",
95 "cyan3",
96 "gray90",
97 /* 8 bright colors */
98 "gray50",
99 "red",
100 "green",
101 "yellow",
102 "#5c5cff",
103 "magenta",
104 "cyan",
105 "white",
106 #else
107 /* 8 normal colors */
108 "#000000",
109 "#b21818",
110 "#18b218",
111 "#b26818",
112 "#1818b2",
113 "#b218b2",
114 "#18b2b2",
115 "#b2b2b2",
116 /* 8 bright colors */
117 "#686868",
118 "#ff5454",
119 "#54ff54",
120 "#ffff54",
121 "#5454ff",
122 "#ff54ff",
123 "#54ffff",
124 "#ffffff",
125 #endif
129 /* more colors can be added after 255 to use with DefaultXX */
130 static const char *defextcolornames[] = {
131 "#cccccc", /* 256 */
132 "#333333", /* 257 */
133 /* root terminal fg and bg */
134 "#809a70", /* 258 */
135 "#002000", /* 259 */
136 /* bold and underline */
137 "#00afaf", /* 260 */
138 "#00af00", /* 261 */
142 /* Default colors (colorname index) foreground, background, cursor, unfocused cursor */
143 #define DEFAULT_FG (7)
144 #define DEFAULT_BG (0)
145 #define DEFAULT_CS (256)
146 #define DEFAULT_UCS (257)
148 #define TNAME "xterm"
150 /* double-click timeout (in milliseconds) between clicks for selection */
151 #define DOUBLECLICK_TIMEOUT (300)
152 #define TRIPLECLICK_TIMEOUT (2*DOUBLECLICK_TIMEOUT)
153 #define SELECT_TIMEOUT 20 /* 20 ms */
154 #define DRAW_TIMEOUT 18 /* 18 ms */
156 #define TAB (8)
159 ////////////////////////////////////////////////////////////////////////////////
160 #define MAX_COLOR (511)
162 /* XEMBED messages */
163 #define XEMBED_FOCUS_IN (4)
164 #define XEMBED_FOCUS_OUT (5)
167 /* Arbitrary sizes */
168 #define ESC_TITLE_SIZ (256)
169 #define ESC_BUF_SIZ (256)
170 #define ESC_ARG_SIZ (16)
171 #define DRAW_BUF_SIZ (2048)
172 #define UTF_SIZ (4)
173 #define OBUFSIZ (256)
174 #define WBUFSIZ (256)
177 /* masks for key translation */
178 #define XK_NO_MOD (UINT_MAX)
179 #define XK_ANY_MOD (0)
182 /* misc utility macros */
183 #define SERRNO strerror(errno)
184 #define MIN(a, b) ((a) < (b) ? (a) : (b))
185 #define MAX(a, b) ((a) < (b) ? (b) : (a))
186 #define LEN(a) (sizeof(a)/sizeof(a[0]))
187 #define DEFAULT(a, b) (a) = ((a) ? (a) : (b))
188 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
189 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
190 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
191 #define IS_SET(flag) (term->mode&(flag))
192 #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000+(t1.tv_usec-t2.tv_usec)/1000)
193 #define X2COL(x) (((x)-opt_border)/xw.cw)
194 #define Y2ROW(y) (((y)-opt_border)/xw.ch-(term!=NULL?term->topline:0))
195 #define IS_GFX(mode) (((mode)&(ATTR_GFX|ATTR_G1)) == ATTR_GFX || ((mode)&(ATTR_GFX1|ATTR_G1)) == (ATTR_GFX1|ATTR_G1))
198 ////////////////////////////////////////////////////////////////////////////////
199 enum glyph_attribute {
200 ATTR_NULL = 0x00,
201 ATTR_REVERSE = 0x01,
202 ATTR_UNDERLINE = 0x02,
203 ATTR_BOLD = 0x04,
204 ATTR_GFX = 0x08,
205 ATTR_DEFFG = 0x10,
206 ATTR_DEFBG = 0x20,
207 ATTR_G1 = 0x40,
208 ATTR_GFX1 = 0x80,
211 enum cursor_movement {
212 CURSOR_UP,
213 CURSOR_DOWN,
214 CURSOR_LEFT,
215 CURSOR_RIGHT,
216 CURSOR_SAVE,
217 CURSOR_LOAD
220 enum cursor_state {
221 CURSOR_DEFAULT = 0,
222 CURSOR_HIDE = 1,
223 CURSOR_WRAPNEXT = 2
226 enum glyph_state {
227 GLYPH_SET = 0x01, /* for selection only */
228 GLYPH_DIRTY = 0x02,
232 enum term_mode {
233 MODE_WRAP = 0x01,
234 MODE_INSERT = 0x02,
235 MODE_APPKEYPAD = 0x04,
236 MODE_ALTSCREEN = 0x08,
237 MODE_CRLF = 0x10,
238 MODE_MOUSEBTN = 0x20,
239 MODE_MOUSEMOTION = 0x40,
240 MODE_MOUSE = 0x20|0x40,
241 MODE_REVERSE = 0x80,
244 enum escape_state {
245 ESC_START = 0x01,
246 ESC_CSI = 0x02,
247 ESC_OSC = 0x04,
248 ESC_TITLE = 0x08,
249 ESC_ALTCHARSET = 0x10,
250 ESC_HASH = 0x20,
251 ESC_PERCENT = 0x40,
252 ESC_ALTG1 = 0x80,
255 enum window_state {
256 WIN_VISIBLE = 0x01,
257 WIN_REDRAW = 0x02,
258 WIN_FOCUSED = 0x04,
261 /* bit macro */
262 #undef B0
263 enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 };
266 ////////////////////////////////////////////////////////////////////////////////
267 typedef unsigned char uchar;
268 typedef unsigned int uint;
269 typedef unsigned long ulong;
270 typedef unsigned short ushort;
273 typedef struct __attribute__((packed)) {
274 char c[UTF_SIZ]; /* character code */
275 uchar mode; /* attribute flags */
276 ushort fg; /* foreground */
277 ushort bg; /* background */
278 uchar state; /* state flags */
279 } Glyph;
282 typedef Glyph *Line;
284 typedef struct {
285 Glyph attr; /* current char attributes */
286 int x;
287 int y;
288 char state;
289 } TCursor;
292 /* CSI Escape sequence structs */
293 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
294 typedef struct {
295 char buf[ESC_BUF_SIZ]; /* raw string */
296 int len; /* raw string length */
297 char priv;
298 int arg[ESC_ARG_SIZ];
299 int narg; /* nb of args */
300 char mode;
301 } CSIEscape;
304 /* Purely graphic info */
305 typedef struct {
306 Display *dpy;
307 Colormap cmap;
308 Window win;
309 Cursor cursor;
310 Cursor blankPtr;
311 Atom xembed;
312 XIM xim;
313 XIC xic;
314 int scr;
315 int w; /* window width */
316 int h; /* window height */
317 int bufw; /* pixmap width */
318 int bufh; /* pixmap height */
319 int ch; /* char height */
320 int cw; /* char width */
321 char state; /* focus, redraw, visible */
323 int tch; /* tab text char height */
324 Pixmap pictab;
325 int tabheight;
326 //struct timeval lastdraw;
327 } XWindow;
330 /* TODO: use better name for vars... */
331 typedef struct {
332 int mode;
333 int bx, by;
334 int ex, ey;
335 struct { int x, y; } b, e;
336 char *clip;
337 Atom xtarget;
338 struct timeval tclick1;
339 struct timeval tclick2;
340 } Selection;
343 /* Drawing Context */
344 typedef struct {
345 ulong *col; //[LEN(colorname) < 256 ? 256 : LEN(colorname)];
346 GC gc;
347 struct {
348 int ascent;
349 int descent;
350 short lbearing;
351 short rbearing;
352 XFontSet set;
353 Font fid;
354 } font[3];
355 } DC;
358 /* Internal representation of the screen */
359 typedef struct {
360 int cmdfd;
361 int dead;
363 int row; /* nb row */
364 int col; /* nb col */
365 int topline; /* top line for drawing (0: no history; 1: show one history line; etc) */
366 int linecount; /* full, with history */
367 int maxhistory;/* max history lines; 0: none; <0: infinite */
368 Line *line; /* screen */
369 Line *alt; /* alternate screen */
370 char *dirty; /* dirtyness of lines */
371 TCursor c; /* cursor */
372 int top; /* top scroll limit */
373 int bot; /* bottom scroll limit */
374 int mode; /* terminal mode flags */
375 int esc; /* escape state flags */
377 TCursor csaved; /* saved cursor info */
378 // old cursor position
379 int oldcx;
380 int oldcy;
382 char title[ESC_TITLE_SIZ+1];
383 int titlelen;
385 int mouseob;
386 int mouseox;
387 int mouseoy;
389 char obuf[OBUFSIZ];
390 #ifdef DUMP_PROG_OUTPUT
391 int xobuflen;
392 #endif
393 int obuflen;
395 char ubuf[UTF_SIZ];
396 int ubufpos;
398 char drawbuf[DRAW_BUF_SIZ];
400 char wrbuf[WBUFSIZ];
401 int wrbufsize;
402 int wrbufused;
403 int wrbufpos;
405 CSIEscape escseq;
406 Selection sel;
407 pid_t pid;
408 int lastDrawTime;
410 char *execcmd;
412 Pixmap picbuf;
413 int picbufw;
414 int picbufh;
416 ushort deffg;
417 ushort defbg;
419 int wantRedraw;
420 } Term;
423 ////////////////////////////////////////////////////////////////////////////////
424 /* Globals */
425 static uchar *unimap = NULL; // 0 or 65535 items; 127: special; high bit set: use alt(gfx) mode; 0: empty
427 static char **opt_cmd = NULL;
428 static char *opt_title = NULL;
429 static char *opt_embed = NULL;
430 static char *opt_class = NULL;
431 static char *opt_term = NULL;
432 static char *opt_fontnorm = NULL;
433 static char *opt_fontbold = NULL;
434 static char *opt_fonttab = NULL;
435 static char *opt_shell = NULL;
436 static char *opt_colornames[512];
437 static int opt_term_locked = 0;
438 static int opt_doubleclick_timeout = DOUBLECLICK_TIMEOUT;
439 static int opt_tripleclick_timeout = TRIPLECLICK_TIMEOUT;
440 static int opt_tabsize = TAB;
441 static int opt_border = BORDER;
442 static int defaultFG = DEFAULT_FG;
443 static int defaultBG = DEFAULT_BG;
444 static int defaultCursorFG = 0;
445 static int defaultCursorBG = DEFAULT_CS;
446 static int defaultCursorInactiveFG = 0;
447 static int defaultCursorInactiveBG = DEFAULT_UCS;
448 static int defaultBoldFG = -1;
449 static int defaultUnderlineFG = -1;
450 static int normalTabFG = 258;
451 static int normalTabBG = 257;
452 static int activeTabFG = 258;
453 static int activeTabBG = 0;
454 static int opt_maxhistory = 512;
455 static int opt_ptrblank = 2000; // delay; 0: never
456 static int ptrBlanked = 0;
457 static uint ptrLastMove = 0;
459 static Term **term_array = NULL;
460 static int term_count = 0;
461 static int term_array_size = 0;
462 static Term *term; // current terminal
463 static int termidx; // current terminal index; DON'T RELAY ON IT!
464 static int updateTabBar;
466 static DC dc;
467 static XWindow xw;
469 static Atom XA_VT_SELECTION;
470 static Atom XA_CLIPBOARD;
471 static Atom XA_UTF8;
472 static Atom XA_TARGETS;
473 static Atom XA_NETWM_NAME;
476 ////////////////////////////////////////////////////////////////////////////////
477 typedef struct {
478 KeySym src;
479 KeySym dst;
480 } KeyTransDef;
483 static KeyTransDef *keytrans = NULL;
484 static int keytrans_size = 0;
485 static int keytrans_used = 0;
488 typedef struct {
489 KeySym key;
490 uint mask;
491 int kp;
492 char *str;
493 } KeyInfoDef;
496 static KeyInfoDef *keybinds = NULL;
497 static int keybinds_size = 0;
498 static int keybinds_used = 0;
500 static KeyInfoDef *keymap = NULL;
501 static int keymap_size = 0;
502 static int keymap_used = 0;
505 ////////////////////////////////////////////////////////////////////////////////
506 static void executeCommands (const char *str);
508 static void ttyresize (void);
509 static void tputc (const char *c); // `c` is utf-8
510 static void ttywrite (const char *s, size_t n);
511 static void tsetdirt (int top, int bot);
512 static void tfulldirt (void);
514 static void xseturgency (int add);
515 static void xfixsel (void);
516 static void xblankPointer (void);
517 static void xunblankPointer (void);
519 static void draw (int forced);
522 static inline void ttywritestr (const char *s) { if (s != NULL && s[0]) ttywrite(s, strlen(s)); }
525 ////////////////////////////////////////////////////////////////////////////////
527 static void trimstr (char *s) {
528 char *e;
530 while (*s && isspace(*s)) ++s;
531 for (e = s+strlen(s); e > s; --e) if (!isspace(e[-1])) break;
532 if (e <= s) *s = 0; else *e = 0;
536 // parse the argument list
537 // return error message or NULL
538 // format:
539 // '*': skip
540 // 's': string (char *)
541 // 'i': integer (int *)
542 // 'b': boolean (int *)
543 // '|': optional arguments follows
544 // '.': stop parsing, ignore rest
545 // 'R': stop parsing, set rest ptr (char *)
546 // string modifiers (also for 'R'):
547 // '!' -- don't allow empty strings
548 // '-' -- trim spaces
549 // int modifiers:
550 // {lo,hi}
551 // {,hi}
552 // {lo}
553 // WARNING! `line` will be modified!
554 // WARNING! string pointers will point INSIDE `line`, so don't discard it!
555 // UGLY! REWRITE!
556 const char *iniParseArguments (char *line, const char *fmt, ...) {
557 va_list ap;
558 int inOptional = 0;
560 if (line == NULL) return "alas";
561 trimstr(line);
562 va_start(ap, fmt);
563 while (*fmt) {
564 char spec = *fmt++, *args;
566 if (spec == '|') { inOptional = 1; continue; }
567 if (spec == '.') { va_end(ap); return NULL; }
569 while (*line && isspace(*line)) ++line;
570 if (*line == '#') *line = 0;
572 if (spec == 'R') {
573 char **p = va_arg(ap, char **);
574 int noempty = 0;
576 while (*fmt) {
577 if (*fmt == '!') { ++fmt; noempty = 1; }
578 else if (*fmt == '-') { ++fmt; trimstr(line); }
579 else break;
581 if (noempty && !line[0]) return "invalid empty arg";
582 if (p != NULL) *p = line;
583 va_end(ap);
584 return NULL;
587 if (!line[0]) {
588 // end of line, stop right here
589 va_end(ap);
590 if (!inOptional) return "out of args";
591 return NULL;
594 args = line;
596 char *dest = args, qch = '#';
597 int n;
599 if (line[0] == '"' || line[0] == '\'') qch = *line++;
601 while (*line && *line != qch) {
602 if (qch == '#' && isspace(*line)) break;
604 if (*line == '\\') {
605 switch (*(++line)) {
606 case 'n': *dest++ = '\n'; ++line; break;
607 case 'r': *dest++ = '\r'; ++line; break;
608 case 't': *dest++ = '\t'; ++line; break;
609 case 'a': *dest++ = '\a'; ++line; break;
610 case 'e': *dest++ = '\x1b'; ++line; break; // esc
611 case 's': *dest++ = ' '; ++line; break;
612 case 'x': // hex
613 ++line;
614 if (!isxdigit(*line)) { va_end(ap); return "invalid hex escape"; }
615 n = toupper(*line)-'0'; if (n > 9) n -= 7;
616 ++line;
617 if (isxdigit(*line)) {
618 int b = toupper(*line)-'0'; if (b > 9) b -= 7;
620 n = (n*16)+b;
621 ++line;
623 *dest++ = n;
624 break;
625 case '0': // octal
626 n = 0;
627 for (int f = 0; f < 4; ++f) {
628 if (*line < '0' || *line > '7') break;
629 n = (n*8)+(line[0]-'0');
630 if (n > 255) { va_end(ap); return "invalid oct escape"; }
631 ++line;
633 if (n == 0) { va_end(ap); return "invalid oct escape"; }
634 *dest++ = n;
635 break;
636 case '1'...'9': // decimal
637 n = 0;
638 for (int f = 0; f < 3; ++f) {
639 if (*line < '0' || *line > '9') break;
640 n = (n*8)+(line[0]-'0');
641 if (n > 255) { va_end(ap); return "invalid dec escape"; }
642 ++line;
644 if (n == 0) { va_end(ap); return "invalid oct escape"; }
645 *dest++ = n;
646 break;
647 default:
648 *dest++ = *line++;
649 break;
651 } else {
652 *dest++ = *line++;
655 if (qch != '#') {
656 if (*line != qch) return "unfinished string";
657 ++line;
658 } else if (*line != '#') ++line;
659 *dest = 0;
661 // now process and convert argument
662 switch (spec) {
663 case '*': /* skip */
664 break;
665 case 's': { /* string */
666 int noempty = 0, trim = 0;
667 char **p;
669 for (;;) {
670 if (*fmt == '!') { noempty = 1; ++fmt; }
671 else if (*fmt == '-') { trim = 1; ++fmt; }
672 else break;
675 if (trim) trimstr(args);
677 if (noempty && !args[0]) { va_end(ap); return "invalid empty string"; }
678 p = va_arg(ap, char **);
679 if (p != NULL) *p = args;
680 } break;
681 case 'i': /* int */
682 if (!args[0]) {
683 va_end(ap);
684 return "invalid integer";
685 } else {
686 int *p = va_arg(ap, int *);
687 long int n;
688 char *eptr;
690 trimstr(args);
691 n = strtol(args, &eptr, 0);
692 if (*eptr) { va_end(ap); return "invalid integer"; }
694 if (*fmt == '{') {
695 // check min/max
696 int minmax[2], haveminmax[2];
698 haveminmax[0] = haveminmax[1] = 0;
699 minmax[0] = minmax[1] = 0;
700 ++fmt;
701 for (int f = 0; f < 2; ++f) {
702 if (isdigit(*fmt) || *fmt == '-' || *fmt == '+') {
703 int neg = 0;
704 haveminmax[f] = 1;
705 if (*fmt == '-') neg = 1;
706 if (!isdigit(*fmt)) {
707 ++fmt;
708 if (!isdigit(*fmt)) { va_end(ap); return "invalid integer bounds"; }
710 while (isdigit(*fmt)) {
711 minmax[f] = minmax[f]*10+(fmt[0]-'0');
712 ++fmt;
714 if (neg) minmax[f] = -minmax[f];
715 //fprintf(stderr, "got: %d\n", minmax[f]);
717 if (*fmt == ',') {
718 if (f == 1) { va_end(ap); return "invalid integer bounds: extra comma"; }
719 // do nothing, we are happy
720 ++fmt;
721 } else if (*fmt == '}') {
722 // ok, done
723 break;
724 } else { va_end(ap); return "invalid integer bounds"; }
726 if (*fmt != '}') { va_end(ap); return "invalid integer bounds"; }
727 ++fmt;
729 //fprintf(stderr, "b: (%d,%d) (%d,%d)\n", haveminmax[0], minmax[0], haveminmax[1], minmax[1]);
730 if ((haveminmax[0] && n < minmax[0]) || (haveminmax[1] && n > minmax[1])) { va_end(ap); return "integer out of bounds"; }
733 if (p) *p = n;
735 break;
736 case 'b': { /* bool */
737 int *p = va_arg(ap, int *);
739 trimstr(args);
740 if (!args[0]) { va_end(ap); return "invalid boolean"; }
741 if (strcasecmp(args, "true") == 0 || strcasecmp(args, "on") == 0 ||
742 strcasecmp(args, "tan") == 0 || strcasecmp(args, "1") == 0) {
743 if (p) *p = 1;
744 } else if (strcasecmp(args, "false") == 0 || strcasecmp(args, "off") == 0 ||
745 strcasecmp(args, "ona") == 0 || strcasecmp(args, "0") == 0) {
747 if (p) *p = 1;
748 } else {
749 va_end(ap);
750 return "invalid boolean";
752 } break;
753 default:
754 va_end(ap);
755 return "invalid format specifier";
758 va_end(ap);
759 while (*line && isspace(*line)) ++line;
760 if (!line[0] || line[0] == '#') return NULL;
761 return "extra args";
765 ////////////////////////////////////////////////////////////////////////////////
766 // UTF-8
767 static int utf8decode (const char *s, long *u) {
768 uchar c;
769 int n, rtn;
771 rtn = 1;
772 c = *s;
773 if (~c & B7) { /* 0xxxxxxx */
774 *u = c;
775 return rtn;
776 } else if ((c & (B7|B6|B5)) == (B7|B6)) { /* 110xxxxx */
777 *u = c&(B4|B3|B2|B1|B0);
778 n = 1;
779 } else if ((c & (B7|B6|B5|B4)) == (B7|B6|B5)) { /* 1110xxxx */
780 *u = c&(B3|B2|B1|B0);
781 n = 2;
782 } else if ((c & (B7|B6|B5|B4|B3)) == (B7|B6|B5|B4)) { /* 11110xxx */
783 *u = c & (B2|B1|B0);
784 n = 3;
785 } else {
786 goto invalid;
788 ++s;
789 for (int f = n; f > 0; --f, ++rtn, ++s) {
790 c = *s;
791 if ((c & (B7|B6)) != B7) goto invalid; /* 10xxxxxx */
792 *u <<= 6;
793 *u |= c & (B5|B4|B3|B2|B1|B0);
795 if ((n == 1 && *u < 0x80) ||
796 (n == 2 && *u < 0x800) ||
797 (n == 3 && *u < 0x10000) ||
798 (*u >= 0xD800 && *u <= 0xDFFF)) {
799 goto invalid;
801 return rtn;
802 invalid:
803 *u = 0xFFFD;
804 return rtn;
808 static int utf8encode (const long *u, char *s) {
809 uchar *sp;
810 ulong uc;
811 int n;
813 sp = (uchar *)s;
814 uc = *u;
815 if (uc < 0x80) {
816 *sp = uc; /* 0xxxxxxx */
817 return 1;
818 } else if (*u < 0x800) {
819 *sp = (uc >> 6) | (B7|B6); /* 110xxxxx */
820 n = 1;
821 } else if (uc < 0x10000) {
822 *sp = (uc >> 12) | (B7|B6|B5); /* 1110xxxx */
823 n = 2;
824 } else if (uc <= 0x10FFFF) {
825 *sp = (uc >> 18) | (B7|B6|B5|B4); /* 11110xxx */
826 n = 3;
827 } else {
828 goto invalid;
830 ++sp;
831 for (int f = n; f > 0; --f, ++sp) *sp = ((uc >> 6*(f-1)) & (B5|B4|B3|B2|B1|B0)) | B7; /* 10xxxxxx */
832 return n+1;
833 invalid:
834 /* U+FFFD */
835 *s++ = '\xEF';
836 *s++ = '\xBF';
837 *s = '\xBD';
838 return 3;
842 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
843 UTF-8 otherwise return 0 */
844 static int isfullutf8 (const char *s, int b) {
845 uchar *c1, *c2, *c3;
847 c1 = (uchar *) s;
848 c2 = (uchar *) ++s;
849 c3 = (uchar *) ++s;
850 if (b < 1) return 0;
851 if ((*c1&(B7|B6|B5)) == (B7|B6) && b == 1) return 0;
852 if ((*c1&(B7|B6|B5|B4)) == (B7|B6|B5) && (b == 1 || (b == 2 && (*c2&(B7|B6)) == B7))) return 0;
853 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;
854 return 1;
858 static int utf8size (const char *s) {
859 uchar c = *s;
861 if (~c&B7) return 1;
862 if ((c&(B7|B6|B5)) == (B7|B6)) return 2;
863 if ((c&(B7|B6|B5|B4)) == (B7|B6|B5)) return 3;
864 return 4;
868 ////////////////////////////////////////////////////////////////////////////////
869 // utilities
870 static char *SPrintfVA (const char *fmt, va_list vaorig) {
871 char *buf = NULL;
872 int olen, len = 128;
874 buf = malloc(len);
875 if (buf == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
876 for (;;) {
877 char *nb;
878 va_list va;
880 va_copy(va, vaorig);
881 olen = vsnprintf(buf, len, fmt, va);
882 va_end(va);
883 if (olen >= 0 && olen < len) return buf;
884 if (olen < 0) olen = len*2-1;
885 nb = realloc(buf, olen+1);
886 if (nb == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
887 buf = nb;
888 len = olen+1;
893 static __attribute__((format(printf,1,2))) char *SPrintf (const char *fmt, ...) {
894 char *buf = NULL;
895 va_list va;
897 va_start(va, fmt);
898 buf = SPrintfVA(fmt, va);
899 va_end(va);
900 return buf;
904 static __attribute__((noreturn)) __attribute__((format(printf,1,2))) void die (const char *errstr, ...) {
905 va_list ap;
907 fprintf(stderr, "FATAL: ");
908 va_start(ap, errstr);
909 vfprintf(stderr, errstr, ap);
910 va_end(ap);
911 fprintf(stderr, "\n");
912 exit(EXIT_FAILURE);
916 ////////////////////////////////////////////////////////////////////////////////
917 static void fixWindowTitle (const Term *t) {
918 const char *title = (t != NULL) ? t->title : NULL;
920 if (title == NULL || !title[0]) {
921 title = opt_title;
922 if (title == NULL) title = "";
924 XStoreName(xw.dpy, xw.win, title);
925 XChangeProperty(xw.dpy, xw.win, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, (const unsigned char *)title, strlen(title));
929 static void switchToTerm (int idx, int redraw) {
930 if (idx >= 0 && idx < term_count && term_array[idx] != NULL && term_array[idx] != term) {
931 termidx = idx;
932 term = term_array[termidx];
933 xseturgency(0);
934 tfulldirt();
935 fixWindowTitle(term);
936 updateTabBar = 1;
937 if (redraw) draw(1);
938 xfixsel();
939 //fprintf(stderr, "term #%d\n", termidx);
944 static Term *termalloc (void) {
945 Term *t;
947 if (term_count >= term_array_size) {
948 int newsz = (term_count==0) ? 1 : term_array_size+64;
949 Term **n = realloc(term_array, sizeof(Term *)*newsz);
951 if (n == NULL && term_count == 0) die("out of memory!");
952 term_array = n;
953 term_array_size = newsz;
955 if ((t = calloc(1, sizeof(Term))) == NULL) return NULL;
956 t->wrbufsize = WBUFSIZ;
957 t->deffg = defaultFG;
958 t->defbg = defaultBG;
959 t->dead = 1;
960 term_array[term_count++] = t;
961 return t;
965 // newer delete last terminal!
966 static void termfree (int idx) {
967 if (idx >= 0 && idx < term_count && term_array[idx] != NULL) {
968 Term *t = term_array[idx];
970 if (t->pid != 0) {
971 kill(t->pid, SIGKILL);
972 return;
974 if (t->cmdfd >= 0) {
975 close(t->cmdfd);
976 t->cmdfd = -1;
978 if (idx == termidx) {
979 if (term_count > 1) {
980 switchToTerm((idx > 0) ? idx-1 : 1, 0);
981 t->dead = 1;
982 return;
984 term = NULL;
986 for (int y = 0; y < t->row; ++y) free(t->alt[y]);
987 for (int y = 0; y < t->linecount; ++y) {
988 //fprintf(stderr, "y=%d\n", y);
989 free(t->line[y]);
991 free(t->dirty);
992 free(t->alt);
993 free(t->line);
994 if (t->execcmd != NULL) free(t->execcmd);
995 // condense array
996 if (termidx > idx) {
997 // not current, and current at the right
998 --termidx;
1000 for (int f = idx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
1001 --term_count;
1002 XFreePixmap(xw.dpy, t->picbuf);
1003 free(t);
1004 updateTabBar = 1;
1005 draw(1);
1010 static void termcleanup (void) {
1011 int f = 0;
1013 while (f < term_count) {
1014 if (term_array[f]->dead) {
1015 termfree(f);
1016 } else {
1017 ++f;
1023 //FIXME: is it safe to assume that signal interrupted main program?
1024 static void sigchld (int a) {
1025 //if (waitpid(term->pid, &stat, 0) < 0) die("waiting for pid %hd failed: %s", term->pid, SERRNO);
1026 for (;;) {
1027 int stat = 0;
1028 pid_t res = waitpid(-1, &stat, WNOHANG);
1030 if (res == (pid_t)-1 || res == 0) break;
1031 //if (WIFEXITED(stat)) exit(WEXITSTATUS(stat));
1032 //exit(EXIT_FAILURE);
1033 for (int f = 0; f < term_count; ++f) {
1034 if (term_array[f]->pid == res) {
1035 // this terminal should die
1036 //if (term_count == 1) exit(0);
1037 //close(term_array[f]->cmdfd);
1038 //term_array[f]->cmdfd = -1;
1039 term_array[f]->dead = 1;
1040 term_array[f]->pid = 0;
1041 updateTabBar = 1;
1042 break;
1046 signal(SIGCHLD, sigchld);
1050 ////////////////////////////////////////////////////////////////////////////////
1051 static void keytrans_reset (void) {
1052 if (keytrans) free(keytrans);
1053 keytrans = NULL;
1054 keytrans_size = 0;
1055 keytrans_used = 0;
1059 static void keytrans_add (const char *src, const char *dst) {
1060 KeySym kssrc = XStringToKeysym(src);
1061 KeySym ksdst = XStringToKeysym(dst);
1063 if (kssrc == NoSymbol) die("invalid keysym: '%s'", src);
1064 if (ksdst == NoSymbol) die("invalid keysym: '%s'", dst);
1065 if (kssrc == ksdst) return; // idiot
1067 for (int f = 0; f < keytrans_used; ++f) {
1068 if (keytrans[f].src == kssrc) {
1069 // replace
1070 keytrans[f].dst = ksdst;
1071 return;
1075 if (keytrans_used >= keytrans_size) {
1076 int newsize = keytrans_size+64;
1077 KeyTransDef *n = realloc(keytrans, sizeof(KeyTransDef)*newsize);
1079 if (n == NULL) die("out of memory");
1080 keytrans_size = newsize;
1081 keytrans = n;
1083 keytrans[keytrans_used].src = kssrc;
1084 keytrans[keytrans_used].dst = ksdst;
1085 ++keytrans_used;
1089 ////////////////////////////////////////////////////////////////////////////////
1090 static void parsekeyname (const char *str, KeySym *ks, uint *mask, int *kp) {
1091 char *s = alloca(strlen(str)+1);
1093 if (s == NULL) die("out of memory");
1094 strcpy(s, str);
1095 *kp = 0;
1096 *ks = NoSymbol;
1097 *mask = XK_NO_MOD;
1099 while (*s) {
1100 char *e, oc;
1101 uint mm = 0;
1102 int mod = 1;
1104 while (*s && isspace(*s)) ++s;
1105 for (e = s; *e && !isspace(*e) && *e != '+'; ++e) ;
1106 oc = *e; *e = 0;
1108 if (strcasecmp(s, "alt") == 0) mm = Mod1Mask;
1109 else if (strcasecmp(s, "win") == 0) mm = Mod4Mask;
1110 else if (strcasecmp(s, "ctrl") == 0) mm = ControlMask;
1111 else if (strcasecmp(s, "shift") == 0) mm = ShiftMask;
1112 else if (strcasecmp(s, "any") == 0) mm = XK_NO_MOD; //!
1113 else if (strcasecmp(s, "kpad") == 0) *kp = 1;
1114 else {
1115 mod = 0;
1116 if ((*ks = XStringToKeysym(s)) == NoSymbol) break;
1117 //fprintf(stderr, "[%s]\n", s);
1120 *e = oc;
1121 s = e;
1122 while (*s && isspace(*s)) ++s;
1123 if (mod) {
1124 if (*s != '+') { *ks = NoSymbol; break; }
1125 ++s;
1126 if (mm != 0) {
1127 if (mm == XK_NO_MOD) *mask = XK_ANY_MOD;
1128 else if (*mask == XK_NO_MOD) *mask = mm;
1129 else if (*mask != XK_ANY_MOD) *mask |= mm;
1131 } else {
1132 if (*s) { *ks = NoSymbol; break; }
1135 if (*ks == NoSymbol) die("invalid key name: '%s'", str);
1136 //fprintf(stderr, "mask=0x%08x, kp=%d\n", *mask, *kp);
1140 ////////////////////////////////////////////////////////////////////////////////
1141 static void keybinds_reset (void) {
1142 if (keybinds) free(keybinds);
1143 keybinds = NULL;
1144 keybinds_size = 0;
1145 keybinds_used = 0;
1149 static void keybind_add (const char *key, const char *act) {
1150 KeySym ks;
1151 uint mask;
1152 int kp;
1154 parsekeyname(key, &ks, &mask, &kp);
1156 for (int f = 0; f < keybinds_used; ++f) {
1157 if (keybinds[f].key == ks && keybinds[f].mask == mask) {
1158 // replace or remove
1159 free(keybinds[f].str);
1160 if (act == NULL || !act[0]) {
1161 // remove
1162 for (int c = f+1; c < keybinds_used; ++c) keybinds[c-1] = keybinds[c];
1163 } else {
1164 // replace
1165 if ((keybinds[f].str = strdup(act)) == NULL) die("out of memory");
1167 return;
1171 if (keybinds_used >= keybinds_size) {
1172 int newsize = keybinds_size+64;
1173 KeyInfoDef *n = realloc(keybinds, sizeof(KeyInfoDef)*newsize);
1175 if (n == NULL) die("out of memory");
1176 keybinds_size = newsize;
1177 keybinds = n;
1179 keybinds[keybinds_used].key = ks;
1180 keybinds[keybinds_used].mask = mask;
1181 keybinds[keybinds_used].kp = 0;
1182 if ((keybinds[keybinds_used].str = strdup(act)) == NULL) die("out of memory");
1183 ++keybinds_used;
1187 ////////////////////////////////////////////////////////////////////////////////
1188 static void keymap_reset (void) {
1189 if (keymap) free(keymap);
1190 keymap = NULL;
1191 keymap_size = 0;
1192 keymap_used = 0;
1196 static void keymap_add (const char *key, const char *act) {
1197 KeySym ks;
1198 uint mask;
1199 int kp;
1201 parsekeyname(key, &ks, &mask, &kp);
1203 for (int f = 0; f < keymap_used; ++f) {
1204 if (keymap[f].key == ks && keymap[f].mask == mask && keymap[f].kp == kp) {
1205 // replace or remove
1206 free(keymap[f].str);
1207 if (act == NULL) {
1208 // remove
1209 for (int c = f+1; c < keymap_used; ++c) keymap[c-1] = keymap[c];
1210 } else {
1211 // replace
1212 if ((keymap[f].str = strdup(act)) == NULL) die("out of memory");
1214 return;
1218 if (keymap_used >= keymap_size) {
1219 int newsize = keymap_size+128;
1220 KeyInfoDef *n = realloc(keymap, sizeof(KeyInfoDef)*newsize);
1222 if (n == NULL) die("out of memory");
1223 keymap_size = newsize;
1224 keymap = n;
1226 keymap[keymap_used].key = ks;
1227 keymap[keymap_used].mask = mask;
1228 keymap[keymap_used].kp = kp;
1229 if ((keymap[keymap_used].str = strdup(act)) == NULL) die("out of memory");
1230 ++keymap_used;
1234 ////////////////////////////////////////////////////////////////////////////////
1235 // locale conversions
1236 static iconv_t icFromLoc;
1237 static iconv_t icToLoc;
1238 static int needConversion = 0;
1241 static void initLCConversion (void) {
1242 const char *lct = setlocale(LC_CTYPE, NULL);
1243 char *cstr;
1245 needConversion = 0;
1246 if (strrchr(lct, '.')) lct = strrchr(lct, '.')+1;
1247 if (strcasecmp(lct, "utf8") == 0 || strcasecmp(lct, "utf-8") == 0) return;
1248 //fprintf(stderr, "locale: [%s]\n", lct);
1249 icFromLoc = iconv_open("UTF-8", lct);
1250 if (icFromLoc == (iconv_t)-1) die("can't initialize locale conversion");
1251 cstr = SPrintf("%s//TRANSLIT", lct);
1252 icToLoc = iconv_open(cstr, "UTF-8");
1253 free(cstr);
1254 if (icToLoc == (iconv_t)-1) die("can't initialize locale conversion");
1255 needConversion = 1;
1259 static int loc2utf (char *dest, const char *src, int len) {
1260 if (needConversion) {
1261 char *ibuf, *obuf;
1262 size_t il, ol, ool;
1264 ibuf = (char *)src;
1265 obuf = dest;
1266 il = len;
1267 ool = ol = il*4;
1268 il = iconv(icFromLoc, &ibuf, &il, &obuf, &ol);
1269 if (il == (size_t)-1) return 0;
1270 return ool-ol;
1271 } else {
1272 if (len > 0) memmove(dest, src, len);
1273 return len;
1278 static int utf2loc (char *dest, const char *src, int len) {
1279 if (needConversion) {
1280 char *ibuf, *obuf;
1281 size_t il, ol, ool;
1283 ibuf = (char *)src;
1284 obuf = dest;
1285 il = len;
1286 ool = ol = il*4;
1287 il = iconv(icToLoc, &ibuf, &il, &obuf, &ol);
1288 if (il == (size_t)-1) return 0;
1289 return ool-ol;
1290 } else {
1291 if (len > 0) memmove(dest, src, len);
1292 return len;
1297 ////////////////////////////////////////////////////////////////////////////////
1298 // getticks
1299 static struct timespec mclk_sttime; // starting time of monotonic clock
1302 static void mclock_init (void) {
1303 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &mclk_sttime);
1307 static uint mclock_ticks (void) {
1308 struct timespec tp;
1310 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &tp);
1311 tp.tv_sec -= mclk_sttime.tv_sec;
1312 return tp.tv_sec*1000+(tp.tv_nsec/1000000);
1316 ////////////////////////////////////////////////////////////////////////////////
1317 // selection
1318 static void inline markDirty (int lineno, int flag) {
1319 if (term != NULL && lineno >= 0 && lineno < term->row) {
1320 term->dirty[lineno] |= flag;
1321 term->wantRedraw = 1;
1322 term->lastDrawTime = mclock_ticks(); //FIXME: avoid excess redraw?
1327 static void selinit (void) {
1328 memset(&term->sel.tclick1, 0, sizeof(term->sel.tclick1));
1329 memset(&term->sel.tclick2, 0, sizeof(term->sel.tclick2));
1330 term->sel.mode = 0;
1331 term->sel.bx = -1;
1332 term->sel.clip = NULL;
1333 term->sel.xtarget = XA_UTF8;
1334 if (term->sel.xtarget == None) term->sel.xtarget = XA_STRING;
1338 static inline int selected (int x, int y) {
1339 if (term->sel.ey == y && term->sel.by == y) {
1340 int bx = MIN(term->sel.bx, term->sel.ex);
1341 int ex = MAX(term->sel.bx, term->sel.ex);
1343 return BETWEEN(x, bx, ex);
1345 return
1346 ((term->sel.b.y < y && y < term->sel.e.y) || (y == term->sel.e.y && x <= term->sel.e.x)) ||
1347 (y == term->sel.b.y && x >= term->sel.b.x && (x <= term->sel.e.x || term->sel.b.y != term->sel.e.y));
1351 static void getbuttoninfo (XEvent *e, int *b, int *x, int *y) {
1352 if (b) *b = e->xbutton.button;
1353 *x = X2COL(e->xbutton.x);
1354 *y = Y2ROW(e->xbutton.y);
1355 term->sel.b.x = term->sel.by < term->sel.ey ? term->sel.bx : term->sel.ex;
1356 term->sel.b.y = MIN(term->sel.by, term->sel.ey);
1357 term->sel.e.x = term->sel.by < term->sel.ey ? term->sel.ex : term->sel.bx;
1358 term->sel.e.y = MAX(term->sel.by, term->sel.ey);
1362 static void mousereport (XEvent *e) {
1363 int x = X2COL(e->xbutton.x);
1364 int y = Y2ROW(e->xbutton.y);
1365 int button = e->xbutton.button;
1366 int state = e->xbutton.state;
1367 //char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
1368 char buf[16];
1370 if (term == NULL) return;
1371 if (x < 0 || x >= term->col || y < 0 || y >= term->row) return;
1372 sprintf(buf, "\x1b[M%c%c%c", 0, 32+x+1, 32+y+1);
1373 /* from urxvt */
1374 if (e->xbutton.type == MotionNotify) {
1375 if (!IS_SET(MODE_MOUSEMOTION) || (x == term->mouseox && y == term->mouseoy)) return;
1376 button = term->mouseob+32;
1377 term->mouseox = x;
1378 term->mouseoy = y;
1379 } else if (e->xbutton.type == ButtonRelease || button == AnyButton) {
1380 button = 3;
1381 } else {
1382 button -= Button1;
1383 if (button >= 3) button += 64-3;
1384 if (e->xbutton.type == ButtonPress) {
1385 term->mouseob = button;
1386 term->mouseox = x;
1387 term->mouseoy = y;
1390 buf[3] = 32+button+(state & ShiftMask ? 4 : 0)+(state & Mod4Mask ? 8 : 0)+(state & ControlMask ? 16 : 0);
1391 ttywrite(buf, 6);
1395 static void xfixsel (void) {
1396 if (term == NULL) return;
1397 if (term->sel.clip != NULL) {
1398 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
1399 XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, xw.win, CurrentTime);
1400 } else {
1401 XSetSelectionOwner(xw.dpy, XA_PRIMARY, None, CurrentTime);
1402 XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, None, CurrentTime);
1404 XFlush(xw.dpy);
1408 static void xsetsel (char *str) {
1409 /* register the selection for both the clipboard and the primary */
1410 if (term == NULL) return;
1411 free(term->sel.clip);
1412 term->sel.clip = str;
1413 xfixsel();
1414 //fprintf(stderr, "[%s]\n", str);
1418 static void selcopy (void) {
1419 char *str, *ptr;
1420 int x, y, bufsize, is_selected = 0;
1422 if (term == NULL || term->sel.bx == -1) {
1423 str = NULL;
1424 } else {
1425 bufsize = (term->col+1)*(term->sel.e.y-term->sel.b.y+1)*UTF_SIZ;
1426 ptr = str = malloc(bufsize);
1427 /* append every set & selected glyph to the selection */
1428 for (y = 0; y < term->row; ++y) {
1429 markDirty(y, 1);
1430 for (x = 0; x < term->col; ++x) {
1431 is_selected = selected(x, y);
1432 if (is_selected) term->line[y][x].state |= GLYPH_DIRTY;
1433 if ((term->line[y][x].state & GLYPH_SET) && is_selected) {
1434 int size = utf8size(term->line[y][x].c);
1435 memcpy(ptr, term->line[y][x].c, size);
1436 ptr += size;
1439 /* \n at the end of every selected line except for the last one */
1440 if (is_selected && y < term->sel.e.y) {
1441 markDirty(y, 2);
1442 *ptr++ = '\n';
1445 *ptr = 0;
1447 xsetsel(str);
1451 static void selnotify (XEvent *e) {
1452 ulong nitems, ofs, rem;
1453 int format;
1454 uchar *data;
1455 Atom type;
1456 XSelectionEvent *se = (XSelectionEvent *)e;
1457 int isutf8;
1459 if (term == NULL) return;
1462 char *name;
1464 fprintf(stderr, "selnotify!\n");
1466 name = se->selection != None ? XGetAtomName(se->display, se->selection) : NULL;
1467 fprintf(stderr, " selection: [%s]\n", name);
1468 if (name != NULL) XFree(name);
1470 name = se->target != None ? XGetAtomName(se->display, se->target) : NULL;
1471 fprintf(stderr, " target: [%s]\n", name);
1472 if (name != NULL) XFree(name);
1474 name = se->property != None ? XGetAtomName(se->display, se->property) : NULL;
1475 fprintf(stderr, " property: [%s]\n", name);
1476 if (name != NULL) XFree(name);
1480 if (se->property != XA_VT_SELECTION) return;
1482 fprintf(stderr, "selection:\n");
1483 fprintf(stderr, " primary: %d\n", se->selection == XA_PRIMARY);
1484 fprintf(stderr, " secondary: %d\n", se->selection == XA_SECONDARY);
1485 fprintf(stderr, " clipboard: %d\n", se->selection == XA_CLIPBOARD);
1486 fprintf(stderr, " vtsel: %d\n", se->selection == XA_VT_SELECTION);
1487 fprintf(stderr, "target:\n");
1488 fprintf(stderr, " primary: %d\n", se->target == XA_PRIMARY);
1489 fprintf(stderr, " secondary: %d\n", se->target == XA_SECONDARY);
1490 fprintf(stderr, " clipboard: %d\n", se->target == XA_CLIPBOARD);
1491 fprintf(stderr, " vtsel: %d\n", se->target == XA_VT_SELECTION);
1493 if (se->target == XA_UTF8) {
1494 isutf8 = 1;
1495 } else if (se->target == XA_STRING) {
1496 isutf8 = 0;
1497 } else {
1498 return;
1500 if (!isutf8) return;
1501 ofs = 0;
1502 do {
1503 if (XGetWindowProperty(xw.dpy, xw.win, se->property, ofs, BUFSIZ/4, False, AnyPropertyType, &type, &format, &nitems, &rem, &data)) {
1504 fprintf(stderr, "Clipboard allocation failed\n");
1505 return;
1507 //fprintf(stderr, "nitems=%d; format=%d; rem=%d\n", (int)nitems, format, (int)rem);
1508 ttywrite((const char *)data, nitems*format/8);
1509 XFree(data);
1510 /* number of 32-bit chunks returned */
1511 ofs += nitems*format/32;
1512 } while (rem > 0);
1516 static void selpaste (Atom which) {
1517 //fprintf(stderr, "selpaste %d!\n", which == XA_PRIMARY);
1518 if (term == NULL) return;
1519 //XConvertSelection(xw.dpy, XA_PRIMARY, term->sel.xtarget, XA_PRIMARY, xw.win, CurrentTime);
1520 if (XGetSelectionOwner(xw.dpy, which) != None) {
1521 XConvertSelection(xw.dpy, which, term->sel.xtarget, XA_VT_SELECTION, xw.win, CurrentTime);
1522 return;
1526 char *name = XGetAtomName(xw.dpy, which);
1528 fprintf(stderr, "no selection owner for [%s]!\n", name);
1529 XFree(name);
1532 if (which == XA_PRIMARY) selpaste(XA_SECONDARY);
1533 else if (which == XA_SECONDARY) selpaste(XA_CLIPBOARD);
1537 static void selrequest (XEvent *e) {
1538 XSelectionRequestEvent *xsre;
1539 XSelectionEvent xev;
1541 if (term == NULL) return;
1542 xsre = (XSelectionRequestEvent *)e;
1543 xev.type = SelectionNotify;
1544 xev.requestor = xsre->requestor;
1545 xev.selection = xsre->selection;
1546 xev.target = xsre->target;
1547 xev.time = xsre->time;
1548 /* reject */
1549 xev.property = None;
1552 char *name;
1554 fprintf(stderr, "selrequest!\n");
1556 name = xsre->selection != None ? XGetAtomName(xsre->display, xsre->selection) : NULL;
1557 fprintf(stderr, " selection: [%s]\n", name);
1558 if (name != NULL) XFree(name);
1560 name = xsre->target != None ? XGetAtomName(xsre->display, xsre->target) : NULL;
1561 fprintf(stderr, " target: [%s]\n", name);
1562 if (name != NULL) XFree(name);
1564 name = xsre->property != None ? XGetAtomName(xsre->display, xsre->property) : NULL;
1565 fprintf(stderr, " property: [%s]\n", name);
1566 if (name != NULL) XFree(name);
1569 if (xsre->target == XA_TARGETS) {
1570 /* respond with the supported type */
1571 Atom string = XA_UTF8;
1573 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_ATOM, 32, PropModeReplace, (uchar *)&string, 1);
1574 xev.property = xsre->property;
1575 } else if (xsre->target == XA_UTF8 && term->sel.clip != NULL) {
1576 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_UTF8, 8, PropModeReplace, (uchar *)term->sel.clip, strlen(term->sel.clip));
1577 xev.property = xsre->property;
1578 } else if (xsre->target == XA_STRING && term->sel.clip != NULL) {
1579 char *s = malloc(strlen(term->sel.clip)*4+8);
1581 if (s != NULL) {
1582 int len = utf2loc(s, term->sel.clip, strlen(term->sel.clip));
1584 XChangeProperty(xsre->display, xsre->requestor, xsre->property, xsre->target, 8, PropModeReplace, (uchar *)s, len);
1585 xev.property = xsre->property;
1586 free(s);
1589 /* all done, send a notification to the listener */
1590 if (!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *)&xev)) fprintf(stderr, "Error sending SelectionNotify event\n");
1594 static void selclear (XEvent *e) {
1595 if (term == NULL) return;
1597 term->sel.mode = 0;
1598 term->sel.ex = term->sel.bx = -1;
1599 term->sel.ey = term->sel.by = -1;
1600 tfulldirt();
1601 draw(1);
1605 static void bpress (XEvent *e) {
1606 xunblankPointer();
1607 ptrLastMove = mclock_ticks();
1609 if (term == NULL) return;
1611 if (e->xbutton.button == Button1 && (e->xbutton.state&ShiftMask) != 0) {
1612 if (term->sel.bx != -1) tsetdirt(term->sel.b.y, term->sel.e.y);
1613 term->sel.mode = 1;
1614 term->sel.ex = term->sel.bx = X2COL(e->xbutton.x);
1615 term->sel.ey = term->sel.by = Y2ROW(e->xbutton.y);
1616 draw(1);
1617 return;
1619 if (e->xbutton.button == Button3 && (e->xbutton.state&ShiftMask) != 0) {
1620 term->sel.bx = -1;
1621 selcopy();
1622 draw(1);
1624 if (IS_SET(MODE_MOUSE)) mousereport(e);
1628 static void brelease (XEvent *e) {
1629 int reportit = 1;
1631 xunblankPointer();
1632 ptrLastMove = mclock_ticks();
1634 if (term == NULL) return;
1636 if ((e->xbutton.state&ShiftMask) != 0) reportit = 0;
1637 if (reportit && IS_SET(MODE_MOUSE)) {
1638 mousereport(e);
1639 return;
1641 if (e->xbutton.button == Button2) {
1642 selpaste(XA_PRIMARY);
1643 } else if (e->xbutton.button == Button1) {
1644 term->sel.mode = 0;
1645 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey);
1646 if (term->sel.bx == term->sel.ex && term->sel.by == term->sel.ey) {
1647 struct timeval now;
1649 markDirty(term->sel.ey, 2);
1650 term->sel.bx = -1;
1651 gettimeofday(&now, NULL);
1652 if (TIMEDIFF(now, term->sel.tclick2) <= opt_tripleclick_timeout) {
1653 /* triple click on the line */
1654 term->sel.b.x = term->sel.bx = 0;
1655 term->sel.e.x = term->sel.ex = term->col;
1656 term->sel.b.y = term->sel.e.y = term->sel.ey;
1657 selcopy();
1658 } else if (TIMEDIFF(now, term->sel.tclick1) <= opt_doubleclick_timeout) {
1659 /* double click to select word */
1660 term->sel.bx = term->sel.ex;
1661 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;
1662 term->sel.b.x = term->sel.bx;
1663 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;
1664 term->sel.e.x = term->sel.ex;
1665 term->sel.b.y = term->sel.e.y = term->sel.ey;
1666 selcopy();
1668 } else {
1669 selcopy();
1672 memcpy(&term->sel.tclick2, &term->sel.tclick1, sizeof(struct timeval));
1673 gettimeofday(&term->sel.tclick1, NULL);
1674 draw(1);
1678 static void bmotion (XEvent *e) {
1679 xunblankPointer();
1680 ptrLastMove = mclock_ticks();
1682 if (term == NULL) return;
1684 if (term->sel.mode) {
1685 int oldey = term->sel.ey, oldex = term->sel.ex;
1687 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey);
1688 if (oldey != term->sel.ey || oldex != term->sel.ex) {
1689 int starty = MIN(oldey, term->sel.ey);
1690 int endy = MAX(oldey, term->sel.ey);
1692 tsetdirt(starty, endy);
1693 draw(1);
1695 return;
1697 if (IS_SET(MODE_MOUSE)) {
1698 mousereport(e);
1699 return;
1704 ////////////////////////////////////////////////////////////////////////////////
1705 // tty init
1706 static inline void setWantRedraw (void) { if (term != NULL) term->wantRedraw = 1; }
1710 static void dump (char c) {
1711 static int col;
1713 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
1714 if (++col % 10 == 0) fprintf(stderr, "\n");
1719 static __attribute__((noreturn)) void execsh (const char *str) {
1720 char **args;
1722 if (str == NULL) {
1723 char *envshell = getenv("SHELL");
1725 DEFAULT(envshell, opt_shell);
1726 setenv("TERM", opt_term, 1);
1727 args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL};
1728 } else {
1729 int argc = 0;
1731 args = calloc(32768, sizeof(char *));
1732 if (args == NULL) exit(EXIT_FAILURE);
1733 while (*str) {
1734 const char *b;
1736 while (*str && isspace(*str)) ++str;
1737 if (!str[0]) break;
1739 b = str;
1740 while (*str && !isspace(*str)) {
1741 if (*str++ == '\\') {
1742 if (*str) ++str;
1746 args[argc] = calloc(str-b+1, 1);
1747 memcpy(args[argc], b, str-b);
1750 FILE *fo = fopen("z.log", "a");
1751 fprintf(fo, "%d: [%s]\n", argc, args[argc]);
1752 fclose(fo);
1755 ++argc;
1757 if (argc < 1) exit(EXIT_FAILURE);
1759 execvp(args[0], args);
1760 exit(EXIT_FAILURE);
1764 static int ttynew (Term *term) {
1765 int m, s;
1766 struct winsize w = {term->row, term->col, 0, 0};
1767 static int signalset = 0;
1769 if (openpty(&m, &s, NULL, NULL, &w) < 0) die("openpty failed: %s", SERRNO);
1770 term->cmdfd = m;
1771 ttyresize();
1772 term->cmdfd = -1;
1773 switch (term->pid = fork()) {
1774 case -1: /* error */
1775 fprintf(stderr, "fork failed");
1776 return -1;
1777 case 0: /* child */
1778 setsid(); /* create a new process group */
1779 dup2(s, STDIN_FILENO);
1780 dup2(s, STDOUT_FILENO);
1781 dup2(s, STDERR_FILENO);
1782 if (ioctl(s, TIOCSCTTY, NULL) < 0) die("ioctl TIOCSCTTY failed: %s", SERRNO);
1783 close(s);
1784 close(m);
1785 execsh(term->execcmd);
1786 break;
1787 default: /* master */
1788 close(s);
1789 term->cmdfd = m;
1790 term->dead = 0;
1791 ttyresize();
1792 if (!signalset) { signalset = 1; signal(SIGCHLD, sigchld); }
1793 break;
1795 return 0;
1799 ////////////////////////////////////////////////////////////////////////////////
1800 // tty r/w
1801 static int ttycanread (void) {
1802 for (;;) {
1803 fd_set rfd;
1804 struct timeval timeout = {0};
1806 if (term->dead || term->cmdfd < 0) return 0;
1807 FD_ZERO(&rfd);
1808 FD_SET(term->cmdfd, &rfd);
1809 if (select(term->cmdfd+1, &rfd, NULL, NULL, &timeout) < 0) {
1810 if (errno == EINTR) continue;
1811 die("select failed: %s", SERRNO);
1813 if (FD_ISSET(term->cmdfd, &rfd)) return 1;
1814 break;
1816 return 0;
1820 static int ttycanwrite (void) {
1821 for (;;) {
1822 fd_set wfd;
1823 struct timeval timeout = {0};
1825 if (term->dead || term->cmdfd < 0) return 0;
1826 FD_ZERO(&wfd);
1827 FD_SET(term->cmdfd, &wfd);
1828 if (select(term->cmdfd+1, NULL, &wfd, NULL, &timeout) < 0) {
1829 if (errno == EINTR) continue;
1830 die("select failed: %s", SERRNO);
1832 if (FD_ISSET(term->cmdfd, &wfd)) return 1;
1833 break;
1835 return 0;
1839 #ifdef DUMP_IO
1840 static void wrstr (const char *s, int len) {
1841 if (s == NULL) return;
1842 while (len-- > 0) {
1843 unsigned char c = (unsigned char)(*s++);
1845 if (c < 32) fprintf(stderr, "{%u}", c); else fwrite(&c, 1, 1, stderr);
1848 #endif
1851 static void ttyread (void) {
1852 char *ptr;
1853 int left;
1855 /* append read bytes to unprocessed bytes */
1856 if (term == NULL || term->dead || term->cmdfd < 0) return;
1857 #ifdef DUMP_PROG_OUTPUT
1858 term->xobuflen = term->obuflen;
1859 #endif
1860 left = OBUFSIZ-term->obuflen;
1861 //dlogf("0: ttyread before: free=%d, used=%d", left, term->obuflen);
1862 while (left > 0 && ttycanread()) {
1863 int ret;
1865 //if ((ret = read(term->cmdfd, term->obuf+term->obuflen, 1)) < 0) die("Couldn't read from shell: %s", SERRNO);
1866 if ((ret = read(term->cmdfd, term->obuf+term->obuflen, left)) < 0) {
1867 //fprintf(stderr, "Warning: couldn't read from tty #%d: %s\n", termidx, SERRNO);
1868 break;
1870 term->obuflen += ret;
1871 left -= ret;
1873 //dlogf("1: ttyread after: free=%d, used=%d", left, term->obuflen);
1874 /* process every complete utf8 char */
1875 #ifdef DUMP_PROG_OUTPUT
1877 FILE *fo = fopen("zlogo.log", "ab");
1878 if (fo) {
1879 fwrite(term->obuf+term->xobuflen, term->obuflen-term->xobuflen, 1, fo);
1880 fclose(fo);
1883 #endif
1884 ptr = term->obuf;
1885 if (needConversion) {
1886 // need conversion from locale to utf-8
1887 //fprintf(stderr, "buf: %d bytes\n", term->obuflen);
1888 while (term->obuflen > 0) {
1889 char obuf[UTF_SIZ+1];
1890 int len;
1892 len = loc2utf(obuf, ptr, 1);
1893 #ifdef DUMP_IO_READ
1895 fprintf(stderr, "rdc: [");
1896 wrstr(ptr, 1);
1897 fprintf(stderr, "] --> [");
1898 wrstr(obuf, len);
1899 fprintf(stderr, "]\n");
1900 fflush(stderr);
1902 #endif
1903 if (len > 0) {
1904 obuf[len] = 0;
1905 tputc(obuf);
1907 ++ptr;
1908 --term->obuflen;
1910 term->obuflen = 0;
1911 } else {
1912 // don't do any conversion
1913 while (term->obuflen >= UTF_SIZ || isfullutf8(ptr, term->obuflen)) {
1914 long utf8c;
1915 char s[UTF_SIZ+1];
1916 int charsize = utf8decode(ptr, &utf8c); /* returns size of utf8 char in bytes */
1917 int len;
1919 len = utf8encode(&utf8c, s);
1920 #ifdef DUMP_IO_READ
1922 fprintf(stderr, "rdx: [");
1923 wrstr(s, len);
1924 fprintf(stderr, "]\n");
1925 fflush(stderr);
1927 #endif
1928 if (len > 0) {
1929 s[len] = 0;
1930 tputc(s);
1932 ptr += charsize;
1933 term->obuflen -= charsize;
1935 //dlogf("2: ttyread afterproc: used=%d", term->obuflen);
1937 /* keep any uncomplete utf8 char for the next call */
1938 if (term->obuflen > 0) memmove(term->obuf, ptr, term->obuflen);
1942 static void ttyflushwrbuf (void) {
1943 if (term == NULL || term->dead || term->cmdfd < 0) return;
1944 if (term->wrbufpos >= term->wrbufused) {
1945 term->wrbufpos = term->wrbufused = 0;
1946 return;
1948 //dlogf("0: ttyflushwrbuf before: towrite=%d", term->wrbufused-term->wrbufpos);
1949 while (term->wrbufpos < term->wrbufused && ttycanwrite()) {
1950 int ret;
1952 if ((ret = write(term->cmdfd, term->wrbuf+term->wrbufpos, term->wrbufused-term->wrbufpos)) == -1) {
1953 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
1955 term->wrbufpos += ret;
1957 if (term->wrbufpos > 0) {
1958 int left = term->wrbufused-term->wrbufpos;
1960 if (left < 1) {
1961 // write buffer is empty
1962 term->wrbufpos = term->wrbufused = 0;
1963 } else {
1964 memmove(term->wrbuf, term->wrbuf+term->wrbufpos, left);
1965 term->wrbufpos = 0;
1966 term->wrbufused = left;
1969 //dlogf("1: ttyflushwrbuf after: towrite=%d", term->wrbufused-term->wrbufpos);
1973 // convert char to locale and write it
1974 static void ttywriterawchar (const char *s, int len) {
1975 char loc[16];
1976 int clen;
1978 if (s == NULL || len < 1) return;
1979 clen = utf2loc(loc, s, len);
1980 if (clen < 1) return;
1981 #ifdef DUMP_IO_WRITE
1983 fprintf(stderr, "wrc: [");
1984 wrstr(s, len);
1985 fprintf(stderr, "] --> [");
1986 wrstr(loc, clen);
1987 fprintf(stderr, "]\n");
1988 fflush(stderr);
1990 #endif
1992 while (term->wrbufused+clen >= term->wrbufsize) {
1993 //FIXME: make write buffer dynamic?
1994 // force write at least one char
1995 //dlogf("ttywrite: forced write");
1996 if (write(term->cmdfd, term->wrbuf+term->wrbufpos, 1) == -1) {
1997 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
1998 } else {
1999 ++term->wrbufpos;
2001 ttyflushwrbuf(); // make room for char
2003 memcpy(term->wrbuf+term->wrbufused, loc, clen);
2004 term->wrbufused += clen;
2008 static void ttywrite (const char *s, size_t n) {
2009 if (term == NULL || term->dead || term->cmdfd < 0) return;
2010 #ifdef DUMP_PROG_INPUT
2011 if (s != NULL && n > 0) {
2012 FILE *fo = fopen("zlogw.log", "ab");
2013 if (fo) {
2014 fwrite(s, n, 1, fo);
2015 fclose(fo);
2018 #endif
2019 //ttyflushwrbuf();
2020 if (s != NULL && n > 0) {
2021 while (n > 0) {
2022 unsigned char c = (unsigned char)(s[0]);
2024 if (term->ubufpos > 0 && isfullutf8(term->ubuf, term->ubufpos)) {
2025 // have complete char
2026 ttywriterawchar(term->ubuf, term->ubufpos);
2027 term->ubufpos = 0;
2028 continue;
2031 if (term->ubufpos == 0) {
2032 // new char
2033 if (c < 128) {
2034 ttywriterawchar(s, 1);
2035 } else if ((c&0xc0) == 0xc0) {
2036 // new utf-8 char
2037 term->ubuf[term->ubufpos++] = *s;
2038 } else {
2039 // ignore unsynced utf-8
2041 ++s;
2042 --n;
2043 continue;
2045 // char continues
2046 if (c < 128 || term->ubufpos >= UTF_SIZ || (c&0xc0) == 0xc0) {
2047 // discard previous utf-8, it's bad
2048 term->ubufpos = 0;
2049 continue;
2051 // collect
2052 term->ubuf[term->ubufpos++] = *s;
2053 ++s;
2054 --n;
2055 if (isfullutf8(term->ubuf, term->ubufpos)) {
2056 // have complete char
2057 ttywriterawchar(term->ubuf, term->ubufpos);
2058 term->ubufpos = 0;
2062 ttyflushwrbuf();
2066 ////////////////////////////////////////////////////////////////////////////////
2067 // tty resize ioctl
2068 static void ttyresize (void) {
2069 struct winsize w;
2071 if (term != NULL && term->cmdfd >= 0) {
2072 w.ws_row = term->row;
2073 w.ws_col = term->col;
2074 w.ws_xpixel = w.ws_ypixel = 0;
2075 if (ioctl(term->cmdfd, TIOCSWINSZ, &w) < 0) fprintf(stderr, "Warning: couldn't set window size: %s\n", SERRNO);
2076 setWantRedraw();
2081 ////////////////////////////////////////////////////////////////////////////////
2082 // tty utilities
2083 static void csidump (void) {
2084 printf("ESC");
2085 for (int f = 1; f < term->escseq.len; ++f) {
2086 uint c = (term->escseq.buf[f]&0xff);
2088 if (isprint(c)) putchar(c);
2089 else if (c == '\n') printf("(\\n)");
2090 else if (c == '\r') printf("(\\r)");
2091 else if (c == 0x1b) printf("(\\e)");
2092 else printf("(%02x)", c);
2094 putchar('\n');
2098 static void tsetdirt (int top, int bot) {
2099 LIMIT(top, 0, term->row-1);
2100 LIMIT(bot, 0, term->row-1);
2101 for (int y = top; y <= bot; ++y) markDirty(y, 2);
2105 static void tfulldirt (void) {
2106 tsetdirt(0, term->row-1);
2110 static void tmoveto (int x, int y) {
2111 LIMIT(x, 0, term->col-1);
2112 LIMIT(y, 0, term->row-1);
2113 term->c.state &= ~CURSOR_WRAPNEXT;
2114 if (term->c.x != x || term->c.y != y) {
2115 term->c.x = x;
2116 term->c.y = y;
2117 setWantRedraw();
2122 static void tclearregion (int x1, int y1, int x2, int y2) {
2123 int temp;
2125 if (x1 > x2) { temp = x1; x1 = x2; x2 = temp; }
2126 if (y1 > y2) { temp = y1; y1 = y2; y2 = temp; }
2127 LIMIT(x1, 0, term->col-1);
2128 LIMIT(x2, 0, term->col-1);
2129 LIMIT(y1, 0, term->row-1);
2130 LIMIT(y2, 0, term->row-1);
2131 for (int y = y1; y <= y2; ++y) {
2132 markDirty(y, (x1 <= 0 && x2 >= term->col-1) ? 2 : 1);
2133 for (int x = x1; x <= x2; ++x) {
2134 term->line[y][x].fg = term->c.attr.fg;
2135 term->line[y][x].bg = term->c.attr.bg;
2136 term->line[y][x].state = GLYPH_DIRTY;
2137 term->line[y][x].mode = ATTR_NULL|(term->c.attr.mode&(ATTR_DEFFG|ATTR_DEFBG));
2138 term->line[y][x].c[0] = ' ';
2144 static void tcursor (int mode) {
2145 if (mode == CURSOR_SAVE) {
2146 term->csaved = term->c;
2147 } else if (mode == CURSOR_LOAD) {
2148 term->c = term->csaved;
2149 tmoveto(term->c.x, term->c.y);
2150 setWantRedraw();
2155 static void treset (void) {
2156 Glyph g;
2158 term->c = (TCursor){{
2159 .mode = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG,
2160 .fg = 0,
2161 .bg = 0
2162 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
2163 term->c.attr.fg = term->deffg;
2164 term->c.attr.bg = term->defbg;
2166 g.state = GLYPH_DIRTY;
2167 g.mode = term->c.attr.mode;
2168 g.fg = term->c.attr.fg;
2169 g.bg = term->c.attr.bg;
2170 g.c[0] = ' ';
2171 g.c[1] = 0;
2173 term->top = 0;
2174 term->bot = term->row-1;
2175 term->mode = MODE_WRAP/* | MODE_MOUSEBTN*/;
2176 //tclearregion(0, 0, term->col-1, term->row-1);
2177 for (int y = 0; y < term->row; ++y) {
2178 markDirty(y, 2);
2179 for (int x = 0; x < term->col; ++x) term->alt[y][x] = term->line[y][x] = g;
2181 for (int y = term->row; y < term->linecount; ++y) {
2182 for (int x = 0; x < term->col; ++x) term->line[y][x] = g;
2184 tcursor(CURSOR_SAVE);
2185 term->topline = 0;
2186 tfulldirt();
2190 static int tinitialize (int col, int row) {
2191 //memset(term, 0, sizeof(Term));
2192 term->wrbufsize = WBUFSIZ;
2193 term->deffg = term->deffg;
2194 term->defbg = term->defbg;
2195 term->row = row;
2196 term->col = col;
2197 term->dirty = calloc(term->row, sizeof(*term->dirty));
2198 term->maxhistory = opt_maxhistory;
2199 term->linecount = term->maxhistory+term->row;
2200 term->line = calloc(term->linecount, sizeof(Line));
2201 term->alt = calloc(term->row, sizeof(Line));
2202 for (int y = 0; y < term->linecount; ++y) term->line[y] = calloc(term->col, sizeof(Glyph));
2203 for (int y = 0; y < term->row; ++y) term->alt[y] = calloc(term->col, sizeof(Glyph));
2204 /* setup screen */
2205 treset();
2206 return 1;
2210 static void tswapscreen (void) {
2211 for (int f = 0; f < term->row; ++f) {
2212 Line t = term->line[f];
2214 term->line[f] = term->alt[f];
2215 term->alt[f] = t;
2217 term->mode ^= MODE_ALTSCREEN;
2218 tfulldirt();
2222 //FIXME: will not work for history
2223 static void selscroll (int orig, int n) {
2224 if (term->sel.bx == -1) return;
2226 if (BETWEEN(term->sel.by, orig, term->bot) || BETWEEN(term->sel.ey, orig, term->bot)) {
2227 if ((term->sel.by += n) > term->bot || (term->sel.ey += n) < term->top) {
2228 term->sel.bx = -1;
2229 return;
2231 if (term->sel.by < term->top) {
2232 term->sel.by = term->top;
2233 term->sel.bx = 0;
2235 if (term->sel.ey > term->bot) {
2236 term->sel.ey = term->bot;
2237 term->sel.ex = term->col;
2239 term->sel.b.y = term->sel.by, term->sel.b.x = term->sel.bx;
2240 term->sel.e.y = term->sel.ey, term->sel.e.x = term->sel.ex;
2245 static void tscrolldown (int orig, int n) {
2246 Line temp;
2248 LIMIT(n, 0, term->bot-orig+1);
2249 //fprintf(stderr, "tscrolldown(%d, %d)\n", orig, n);
2250 tclearregion(0, term->bot-n+1, term->col-1, term->bot);
2251 for (int f = term->bot; f >= orig+n; --f) {
2252 temp = term->line[f];
2253 term->line[f] = term->line[f-n];
2254 term->line[f-n] = temp;
2255 markDirty(f, 2);
2256 markDirty(f-n, 2);
2258 selscroll(orig, n);
2262 static void tscrollup (int orig, int n) {
2263 Line temp;
2265 if (term == NULL) return;
2266 LIMIT(n, 0, term->bot-orig+1);
2267 if (n < 1) return;
2268 //fprintf(stderr, "tscrollup(%d, %d)\n", orig, n);
2269 if (n == 1 && orig == 0 && term->bot >= term->row-1 && term->maxhistory > 0) {
2270 Line l = term->line[term->linecount-1];
2272 for (int f = term->linecount-1; f > term->row; --f) term->line[f] = term->line[f-1];
2273 term->line[term->row] = l;
2274 for (int x = 0; x < term->col; ++x) l[x] = term->line[0][x];
2277 tclearregion(0, orig, term->col-1, orig+n-1);
2279 for (int f = orig; f <= term->bot-n; ++f) {
2280 temp = term->line[f];
2281 term->line[f] = term->line[f+n];
2282 term->line[f+n] = temp;
2283 markDirty(f, 2);
2284 markDirty(f+n, 2);
2286 selscroll(orig, -n);
2290 static void tnewline (int first_col) {
2291 int y = term->c.y;
2293 if (y == term->bot) tscrollup(term->top, 1); else ++y;
2294 tmoveto(first_col ? 0 : term->c.x, y);
2298 static void csiparse (void) {
2299 const char *p = term->escseq.buf;
2301 term->escseq.narg = 0;
2302 if (*p == '?') { term->escseq.priv = 1; ++p; }
2303 while (p < term->escseq.buf+term->escseq.len) {
2304 int n = term->escseq.arg[term->escseq.narg];
2306 for (; *p && isdigit(*p); ++p) n = n*10+(p[0]-'0');
2307 term->escseq.arg[term->escseq.narg] = n;
2309 if (*p == ';' && term->escseq.narg+1 < ESC_ARG_SIZ) {
2310 ++term->escseq.narg;
2311 ++p;
2312 } else {
2313 term->escseq.mode = *p;
2314 ++term->escseq.narg;
2315 break;
2321 static void tsetchar (const char *c) {
2322 char ub[UTF_SIZ+1];
2323 int rev = 0, gfx = 0;
2324 int x = term->c.x, y = term->c.y;
2326 if (!needConversion && unimap != NULL) {
2327 long cc;
2329 utf8decode(c, &cc);
2330 if (cc >= 0 && cc <= 65535) {
2331 uchar uc = unimap[cc];
2333 if (uc) {
2334 if (uc == 127) {
2335 // inversed space
2336 rev = 1;
2337 ub[0] = ' ';
2338 } else {
2339 if (uc&0x80) {
2340 ub[0] = (uc&0x7f);
2341 gfx = 1;
2342 } else {
2343 ub[0] = uc&0x7f;
2346 ub[1] = 0;
2347 c = ub;
2352 if (rev || gfx || (term->line[y][x].state & GLYPH_SET) == 0 || ATTRCMP(term->line[y][x], term->c.attr)) {
2353 term->line[y][x] = term->c.attr;
2354 if (rev) term->line[y][x].mode ^= ATTR_REVERSE;
2355 if (gfx) term->line[y][x].mode |= ATTR_GFX;
2356 } else {
2357 int clen = utf8size(c), olen = utf8size(term->line[y][x].c);
2359 if (clen < 1 || (clen == olen && memcmp(c, term->line[y][x].c, clen) == 0)) return;
2362 markDirty(y, 1);
2364 term->line[y][x] = term->c.attr;
2365 if (rev) term->line[y][x].mode ^= ATTR_REVERSE;
2366 if (gfx) {
2367 term->line[y][x].mode &= ~(ATTR_GFX|ATTR_GFX1);
2368 term->line[y][x].mode |= (term->line[y][x].mode&ATTR_G1) ? ATTR_GFX1 : ATTR_GFX;
2371 term->line[y][x].state |= (GLYPH_SET | GLYPH_DIRTY);
2372 memmove(term->line[y][x].c, c, UTF_SIZ);
2374 if (IS_GFX(term->line[y][x].mode)) {
2375 unsigned char c = (unsigned char)(term->line[y][x].c[0]);
2377 if (c > 95 && c < 128) term->line[y][x].c[0] -= 95;
2378 else if (c > 127) term->line[y][x].c[0] = ' ';
2380 //dlogf("tsetchar(%d,%d): [%c] (%d); dirty=%d\n", x, y, c[0], term->wantRedraw, term->dirty[y]);
2384 static void tdeletechar (int n) {
2385 int src = term->c.x+n;
2386 int dst = term->c.x;
2387 int size = term->col-src;
2389 markDirty(term->c.y, 2);
2390 if (src >= term->col) {
2391 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2392 } else {
2393 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2394 tclearregion(term->col-n, term->c.y, term->col-1, term->c.y);
2399 static void tinsertblank (int n) {
2400 int src = term->c.x;
2401 int dst = src+n;
2402 int size = term->col-dst;
2404 markDirty(term->c.y, 2);
2405 if (dst >= term->col) {
2406 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2407 } else {
2408 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2409 tclearregion(src, term->c.y, dst-1, term->c.y);
2414 static void tinsertblankline (int n) {
2415 if (term->c.y < term->top || term->c.y > term->bot) return;
2416 tscrolldown(term->c.y, n);
2420 static void tdeleteline (int n) {
2421 if (term->c.y < term->top || term->c.y > term->bot) return;
2422 tscrollup(term->c.y, n);
2426 static void tsetattr (int *attr, int l) {
2427 for (int f = 0; f < l; ++f) {
2428 switch (attr[f]) {
2429 case 0:
2430 term->c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
2431 term->c.attr.mode |= ATTR_DEFFG | ATTR_DEFBG;
2432 term->c.attr.fg = term->deffg;
2433 term->c.attr.bg = term->defbg;
2434 break;
2435 case 1:
2436 term->c.attr.mode |= ATTR_BOLD;
2437 break;
2438 case 4:
2439 term->c.attr.mode |= ATTR_UNDERLINE;
2440 break;
2441 case 7:
2442 term->c.attr.mode |= ATTR_REVERSE;
2443 break;
2444 case 22:
2445 term->c.attr.mode &= ~ATTR_BOLD;
2446 break;
2447 case 24:
2448 term->c.attr.mode &= ~ATTR_UNDERLINE;
2449 break;
2450 case 27:
2451 term->c.attr.mode &= ~ATTR_REVERSE;
2452 break;
2453 case 38:
2454 if (f+2 < l && attr[f+1] == 5) {
2455 f += 2;
2456 if (BETWEEN(attr[f], 0, 255)) {
2457 term->c.attr.fg = attr[f];
2458 term->c.attr.mode &= ~ATTR_DEFFG;
2459 } else {
2460 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[f]);
2462 } else {
2463 //fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2464 term->c.attr.fg = term->deffg;
2465 term->c.attr.mode |= ATTR_DEFFG;
2467 break;
2468 case 39:
2469 term->c.attr.fg = term->deffg;
2470 term->c.attr.mode |= ATTR_DEFFG;
2471 break;
2472 case 48:
2473 if (f+2 < l && attr[f+1] == 5) {
2474 f += 2;
2475 if (BETWEEN(attr[f], 0, 255)) {
2476 term->c.attr.bg = attr[f];
2477 term->c.attr.mode &= ~ATTR_DEFBG;
2478 } else {
2479 fprintf(stderr, "erresc: bad bgcolor %d\n", attr[f]);
2481 } else {
2482 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2484 break;
2485 case 49:
2486 term->c.attr.bg = term->defbg;
2487 term->c.attr.mode |= ATTR_DEFBG;
2488 break;
2489 default:
2490 if (BETWEEN(attr[f], 30, 37)) { term->c.attr.fg = attr[f]-30; term->c.attr.mode &= ~ATTR_DEFFG; }
2491 else if (BETWEEN(attr[f], 40, 47)) { term->c.attr.bg = attr[f]-40; term->c.attr.mode &= ~ATTR_DEFBG; }
2492 else if (BETWEEN(attr[f], 90, 97)) { term->c.attr.fg = attr[f]-90+8; term->c.attr.mode &= ~ATTR_DEFFG; }
2493 else if (BETWEEN(attr[f], 100, 107)) { term->c.attr.fg = attr[f]-100+8; term->c.attr.mode &= ~ATTR_DEFFG; }
2494 else { fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]); csidump(); }
2495 break;
2501 static void tsetscroll (int t, int b) {
2502 int temp;
2504 LIMIT(t, 0, term->row-1);
2505 LIMIT(b, 0, term->row-1);
2506 if (t > b) {
2507 temp = t;
2508 t = b;
2509 b = temp;
2511 term->top = t;
2512 term->bot = b;
2516 ////////////////////////////////////////////////////////////////////////////////
2517 // esc processing
2518 static void csihandle (void) {
2519 switch (term->escseq.mode) {
2520 case '@': /* ICH -- Insert <n> blank char */
2521 DEFAULT(term->escseq.arg[0], 1);
2522 tinsertblank(term->escseq.arg[0]);
2523 break;
2524 case 'A': /* CUU -- Cursor <n> Up */
2525 case 'e':
2526 DEFAULT(term->escseq.arg[0], 1);
2527 tmoveto(term->c.x, term->c.y-term->escseq.arg[0]);
2528 break;
2529 case 'B': /* CUD -- Cursor <n> Down */
2530 DEFAULT(term->escseq.arg[0], 1);
2531 tmoveto(term->c.x, term->c.y+term->escseq.arg[0]);
2532 break;
2533 case 'C': /* CUF -- Cursor <n> Forward */
2534 case 'a':
2535 DEFAULT(term->escseq.arg[0], 1);
2536 tmoveto(term->c.x+term->escseq.arg[0], term->c.y);
2537 break;
2538 case 'D': /* CUB -- Cursor <n> Backward */
2539 DEFAULT(term->escseq.arg[0], 1);
2540 tmoveto(term->c.x-term->escseq.arg[0], term->c.y);
2541 break;
2542 case 'E': /* CNL -- Cursor <n> Down and first col */
2543 DEFAULT(term->escseq.arg[0], 1);
2544 tmoveto(0, term->c.y+term->escseq.arg[0]);
2545 break;
2546 case 'F': /* CPL -- Cursor <n> Up and first col */
2547 DEFAULT(term->escseq.arg[0], 1);
2548 tmoveto(0, term->c.y-term->escseq.arg[0]);
2549 break;
2550 case 'G': /* CHA -- Move to <col> */
2551 case '`': /* XXX: HPA -- same? */
2552 DEFAULT(term->escseq.arg[0], 1);
2553 tmoveto(term->escseq.arg[0]-1, term->c.y);
2554 break;
2555 case 'H': /* CUP -- Move to <row> <col> */
2556 case 'f': /* XXX: HVP -- same? */
2557 DEFAULT(term->escseq.arg[0], 1);
2558 DEFAULT(term->escseq.arg[1], 1);
2559 tmoveto(term->escseq.arg[1]-1, term->escseq.arg[0]-1);
2560 break;
2561 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
2562 case 'J': /* ED -- Clear screen */
2563 term->sel.bx = -1;
2564 switch (term->escseq.arg[0]) {
2565 case 0: /* below */
2566 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2567 if (term->c.y < term->row-1) tclearregion(0, term->c.y+1, term->col-1, term->row-1);
2568 break;
2569 case 1: /* above */
2570 if (term->c.y > 1) tclearregion(0, 0, term->col-1, term->c.y-1);
2571 tclearregion(0, term->c.y, term->c.x, term->c.y);
2572 break;
2573 case 2: /* all */
2574 tclearregion(0, 0, term->col-1, term->row-1);
2575 break;
2576 default:
2577 goto unknown;
2579 break;
2580 case 'K': /* EL -- Clear line */
2581 switch (term->escseq.arg[0]) {
2582 case 0: /* right */
2583 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2584 break;
2585 case 1: /* left */
2586 tclearregion(0, term->c.y, term->c.x, term->c.y);
2587 break;
2588 case 2: /* all */
2589 tclearregion(0, term->c.y, term->col-1, term->c.y);
2590 break;
2592 break;
2593 case 'S': /* SU -- Scroll <n> line up */
2594 DEFAULT(term->escseq.arg[0], 1);
2595 tscrollup(term->top, term->escseq.arg[0]);
2596 break;
2597 case 'T': /* SD -- Scroll <n> line down */
2598 DEFAULT(term->escseq.arg[0], 1);
2599 tscrolldown(term->top, term->escseq.arg[0]);
2600 break;
2601 case 'L': /* IL -- Insert <n> blank lines */
2602 DEFAULT(term->escseq.arg[0], 1);
2603 tinsertblankline(term->escseq.arg[0]);
2604 break;
2605 case 'l': /* RM -- Reset Mode */
2606 if (term->escseq.priv) {
2607 switch (term->escseq.arg[0]) {
2608 case 1: // 1001 for xterm compatibility
2609 DUMP_KEYPAD_SWITCH("1", "OFF");
2610 term->mode &= ~MODE_APPKEYPAD;
2611 break;
2612 case 5: /* DECSCNM -- Remove reverse video */
2613 if (IS_SET(MODE_REVERSE)) {
2614 term->mode &= ~MODE_REVERSE;
2615 tfulldirt();
2617 break;
2618 case 7: /* autowrap off */
2619 term->mode &= ~MODE_WRAP;
2620 break;
2621 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
2622 break;
2623 case 20: /* non-standard code? */
2624 term->mode &= ~MODE_CRLF;
2625 break;
2626 case 25: /* hide cursor */
2627 term->c.state |= CURSOR_HIDE;
2628 break;
2629 case 1000: /* disable X11 xterm mouse reporting */
2630 term->mode &= ~MODE_MOUSEBTN;
2631 break;
2632 case 1002:
2633 term->mode &= ~MODE_MOUSEMOTION;
2634 break;
2635 case 1049: /* = 1047 and 1048 */
2636 case 47:
2637 case 1047:
2638 if (IS_SET(MODE_ALTSCREEN)) {
2639 tclearregion(0, 0, term->col-1, term->row-1);
2640 tswapscreen();
2642 if (term->escseq.arg[0] != 1049) break;
2643 case 1048:
2644 tcursor(CURSOR_LOAD);
2645 break;
2646 default:
2647 goto unknown;
2649 } else {
2650 switch (term->escseq.arg[0]) {
2651 case 4:
2652 term->mode &= ~MODE_INSERT;
2653 break;
2654 default:
2655 goto unknown;
2658 break;
2659 case 'M': /* DL -- Delete <n> lines */
2660 DEFAULT(term->escseq.arg[0], 1);
2661 tdeleteline(term->escseq.arg[0]);
2662 break;
2663 case 'X': /* ECH -- Erase <n> char */
2664 DEFAULT(term->escseq.arg[0], 1);
2665 tclearregion(term->c.x, term->c.y, term->c.x + term->escseq.arg[0], term->c.y);
2666 break;
2667 case 'P': /* DCH -- Delete <n> char */
2668 DEFAULT(term->escseq.arg[0], 1);
2669 tdeletechar(term->escseq.arg[0]);
2670 break;
2671 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
2672 case 'd': /* VPA -- Move to <row> */
2673 DEFAULT(term->escseq.arg[0], 1);
2674 tmoveto(term->c.x, term->escseq.arg[0]-1);
2675 break;
2676 case 'h': /* SM -- Set terminal mode */
2677 if (term->escseq.priv) {
2678 switch (term->escseq.arg[0]) {
2679 case 1:
2680 DUMP_KEYPAD_SWITCH("1", "ON");
2681 term->mode |= MODE_APPKEYPAD;
2682 break;
2683 case 5: /* DECSCNM -- Reverve video */
2684 if (!IS_SET(MODE_REVERSE)) {
2685 term->mode |= MODE_REVERSE;
2686 tfulldirt();
2688 break;
2689 case 7:
2690 term->mode |= MODE_WRAP;
2691 break;
2692 case 20:
2693 term->mode |= MODE_CRLF;
2694 break;
2695 case 12: /* att610 -- Start blinking cursor (IGNORED) */
2696 /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
2697 if (term->escseq.narg > 1 && term->escseq.arg[1] != 25) break;
2698 case 25:
2699 term->c.state &= ~CURSOR_HIDE;
2700 break;
2701 case 1000: /* 1000,1002: enable xterm mouse report */
2702 term->mode |= MODE_MOUSEBTN;
2703 break;
2704 case 1002:
2705 term->mode |= MODE_MOUSEMOTION;
2706 break;
2707 case 1049: /* = 1047 and 1048 */
2708 case 47:
2709 case 1047:
2710 if (IS_SET(MODE_ALTSCREEN)) tclearregion(0, 0, term->col-1, term->row-1); else tswapscreen();
2711 if (term->escseq.arg[0] != 1049) break;
2712 case 1048:
2713 tcursor(CURSOR_SAVE);
2714 break;
2715 default: goto unknown;
2717 } else {
2718 switch (term->escseq.arg[0]) {
2719 case 4:
2720 term->mode |= MODE_INSERT;
2721 break;
2722 default:
2723 goto unknown;
2726 break;
2727 case 'm': /* SGR -- Terminal attribute (color) */
2728 tsetattr(term->escseq.arg, term->escseq.narg);
2729 break;
2730 case 'r': /* DECSTBM -- Set Scrolling Region */
2731 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
2732 // xterm compatibility
2733 DUMP_KEYPAD_SWITCH("1001", "OFF");
2734 term->mode &= ~MODE_APPKEYPAD;
2735 } else if (term->escseq.priv) {
2736 goto unknown;
2737 } else {
2738 DEFAULT(term->escseq.arg[0], 1);
2739 DEFAULT(term->escseq.arg[1], term->row);
2740 tsetscroll(term->escseq.arg[0]-1, term->escseq.arg[1]-1);
2741 tmoveto(0, 0);
2743 break;
2744 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
2745 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
2746 // xterm compatibility
2747 DUMP_KEYPAD_SWITCH("1001", "ON");
2748 term->mode |= MODE_APPKEYPAD;
2749 } else {
2750 tcursor(CURSOR_SAVE);
2752 break;
2753 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
2754 tcursor(CURSOR_LOAD);
2755 break;
2756 default:
2757 unknown:
2758 fprintf(stderr, "erresc: unknown csi ");
2759 csidump();
2760 break;
2765 static void csireset (void) {
2766 memset(&term->escseq, 0, sizeof(term->escseq));
2770 static void tputtab (void) {
2771 int space = opt_tabsize-term->c.x%opt_tabsize;
2773 if (space > 0) tmoveto(term->c.x+space, term->c.y);
2777 ////////////////////////////////////////////////////////////////////////////////
2778 // put char to output buffer or process command
2779 static void tputc (const char *c) {
2780 char ascii = *c;
2782 //dlogf("tputc: [%c]\n", c[0]);
2783 if (term->esc & ESC_START) {
2784 if (term->esc & ESC_CSI) {
2785 term->escseq.buf[term->escseq.len++] = ascii;
2786 if (BETWEEN(ascii, 0x40, 0x7E) || term->escseq.len >= ESC_BUF_SIZ) {
2787 term->esc = 0;
2788 csiparse();
2789 csihandle();
2791 } else if (term->esc & ESC_OSC) {
2792 /* TODO: handle other OSC */
2793 if (ascii == ';') {
2794 term->title[0] = 0;
2795 term->titlelen = 0;
2796 term->esc = ESC_START | ESC_TITLE;
2797 //updateTabBar = 1;
2799 } else if (term->esc & ESC_TITLE) {
2800 int len = utf8size(c);
2802 if (ascii == '\a' || term->titlelen+len >= ESC_TITLE_SIZ) {
2803 term->esc = 0;
2804 term->title[term->titlelen] = '\0';
2805 fixWindowTitle(term);
2806 updateTabBar = 1;
2807 } else if (len > 0) {
2808 memcpy(term->title+term->titlelen, c, len);
2809 term->titlelen += len;
2810 term->title[term->titlelen] = '\0';
2812 } else if (term->esc & ESC_ALTCHARSET) {
2813 term->esc = 0;
2814 switch (ascii) {
2815 case '0': /* Line drawing crap */
2816 term->c.attr.mode |= ATTR_GFX;
2817 break;
2818 case 'B': /* Back to regular text */
2819 term->c.attr.mode &= ~ATTR_GFX;
2820 break;
2821 default:
2822 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2823 term->c.attr.mode &= ~ATTR_GFX;
2824 break;
2826 } else if (term->esc & ESC_ALTG1) {
2827 term->esc = 0;
2828 switch (ascii) {
2829 case '0': /* Line drawing crap */
2830 term->c.attr.mode |= ATTR_GFX1;
2831 break;
2832 case 'B': /* Back to regular text */
2833 term->c.attr.mode &= ~ATTR_GFX1;
2834 break;
2835 default:
2836 fprintf(stderr, "esc unhandled charset: ESC ) %c\n", ascii);
2837 term->c.attr.mode &= ~ATTR_GFX1;
2838 break;
2840 } else if (term->esc & ESC_HASH) {
2841 term->esc = 0;
2842 switch (ascii) {
2843 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
2844 //tfillscreenwithE();
2845 break;
2847 } else if (term->esc & ESC_PERCENT) {
2848 term->esc = 0;
2849 } else {
2850 switch (ascii) {
2851 case '[': term->esc |= ESC_CSI; break;
2852 case ']': term->esc |= ESC_OSC; break;
2853 case '(': term->esc |= ESC_ALTCHARSET; break;
2854 case ')': term->esc |= ESC_ALTG1; break;
2855 case '#': term->esc |= ESC_HASH; break;
2856 case '%': term->esc |= ESC_PERCENT; break;
2857 case 'D': /* IND -- Linefeed */
2858 term->esc = 0;
2859 if (term->c.y == term->bot) tscrollup(term->top, 1); else tmoveto(term->c.x, term->c.y+1);
2860 break;
2861 case 'E': /* NEL -- Next line */
2862 term->esc = 0;
2863 tnewline(1); /* always go to first col */
2864 break;
2865 case 'M': /* RI -- Reverse linefeed */
2866 term->esc = 0;
2867 if (term->c.y == term->top) tscrolldown(term->top, 1); else tmoveto(term->c.x, term->c.y-1);
2868 break;
2869 case 'c': /* RIS -- Reset to inital state */
2870 term->esc = 0;
2871 treset();
2872 break;
2873 case '=': /* DECPAM -- Application keypad */
2874 DUMP_KEYPAD_SWITCH("=", "ON");
2875 term->esc = 0;
2876 term->mode |= MODE_APPKEYPAD;
2877 break;
2878 case '>': /* DECPNM -- Normal keypad */
2879 DUMP_KEYPAD_SWITCH(">", "OFF");
2880 term->esc = 0;
2881 term->mode &= ~MODE_APPKEYPAD;
2882 break;
2883 case '7': /* DECSC -- Save Cursor */
2884 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
2885 //TODO?
2886 term->esc = 0;
2887 tcursor(CURSOR_SAVE);
2888 break;
2889 case '8': /* DECRC -- Restore Cursor */
2890 //TODO?
2891 term->esc = 0;
2892 tcursor(CURSOR_LOAD);
2893 break;
2894 case 'Z': /* DEC private identification */
2895 term->esc = 0;
2896 ttywritestr("\x1b[?1;2c");
2897 break;
2898 default:
2899 term->esc = 0;
2900 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X ('%c')\n", (uchar)ascii, isprint(ascii)?ascii:'.');
2901 break;
2904 } else {
2905 if (term->sel.bx != -1 && BETWEEN(term->c.y, term->sel.by, term->sel.ey)) term->sel.bx = -1;
2906 switch (ascii) {
2907 case '\t': tputtab(); break;
2908 case '\b': tmoveto(term->c.x-1, term->c.y); break;
2909 case '\r': tmoveto(0, term->c.y); break;
2910 case '\v': tnewline(0); break;
2911 case '\f': case '\n': tnewline(IS_SET(MODE_CRLF)); break; /* go to first col if the mode is set */
2912 case '\a': if (!(xw.state & WIN_FOCUSED)) xseturgency(1); XBell(xw.dpy, 100); break;
2913 case 14: term->c.attr.mode |= ATTR_G1; break;
2914 case 15: term->c.attr.mode &= ~ATTR_G1; break;
2915 case '\033': csireset(); term->esc = ESC_START; break;
2916 default:
2917 if (needConversion && IS_GFX(term->c.attr.mode)) {
2918 long cc;
2920 utf8decode(c, &cc);
2921 if (cc < 32 || cc >= 127) break; //FIXME: nothing at all
2922 } else {
2923 if ((unsigned char)ascii < 32 || ascii == 127) break; // seems that this chars are empty too
2925 if (IS_SET(MODE_WRAP) && (term->c.state&CURSOR_WRAPNEXT)) tnewline(1); /* always go to first col */
2926 tsetchar(c);
2927 if (term->c.x+1 < term->col) tmoveto(term->c.x+1, term->c.y); else term->c.state |= CURSOR_WRAPNEXT;
2928 break;
2934 ////////////////////////////////////////////////////////////////////////////////
2935 // tty resising
2936 static int tresize (int col, int row) {
2937 int mincol = MIN(col, term->col);
2938 int slide = term->c.y-row+1;
2939 Glyph g;
2941 if (col < 1 || row < 1) return 0;
2943 g.state = GLYPH_DIRTY;
2944 g.mode = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG;
2945 g.fg = term->deffg;
2946 g.bg = term->defbg;
2947 g.c[0] = ' ';
2948 g.c[1] = 0;
2950 if (slide > 0) {
2951 tsetscroll(0, term->row-1);
2952 for (; slide > 0; --slide) tscrollup(0, 1); // to fill history
2955 if (row < term->row) {
2956 /* free unneeded rows */
2957 for (int f = row; f < term->row; ++f) free(term->alt[f]);
2958 for (int f = term->linecount-(term->row-row); f < term->linecount; ++f) free(term->line[f]);
2959 term->linecount -= (term->row-row);
2960 /* resize to new height */
2961 term->alt = realloc(term->alt, row*sizeof(Line));
2962 term->line = realloc(term->line, term->linecount*sizeof(Line));
2963 } else if (row > term->row) {
2964 /* resize to new height */
2965 term->alt = realloc(term->alt, row*sizeof(Line));
2966 term->line = realloc(term->line, (row+term->maxhistory)*sizeof(Line));
2967 /* add more lines */
2968 for (int f = term->row; f < row; ++f) {
2969 term->alt[f] = calloc(col, sizeof(Glyph));
2970 for (int x = 0; x < col; ++x) term->alt[f][x] = g;
2972 for (int f = 0; f < row-term->row; ++f) {
2973 int y = term->linecount++;
2975 term->line[y] = calloc(col, sizeof(Glyph));
2976 for (int x = 0; x < col; ++x) term->line[y][x] = g;
2980 if (row != term->row) {
2981 term->dirty = realloc(term->dirty, row*sizeof(*term->dirty));
2984 /* resize each row to new width, zero-pad if needed */
2985 for (int f = 0; f < term->linecount; ++f) {
2986 term->line[f] = realloc(term->line[f], col*sizeof(Glyph));
2987 for (int x = mincol; x < col; ++x) term->line[f][x] = g;
2988 if (f < row) {
2989 markDirty(f, 2);
2990 term->alt[f] = realloc(term->alt[f], col*sizeof(Glyph));
2991 for (int x = mincol; x < col; ++x) term->alt[f][x] = g;
2994 /* update terminal size */
2995 term->topline = 0;
2996 term->col = col;
2997 term->row = row;
2998 /* make use of the LIMIT in tmoveto */
2999 tmoveto(term->c.x, term->c.y);
3000 /* reset scrolling region */
3001 tsetscroll(0, row-1);
3002 tfulldirt();
3003 return (slide > 0);
3007 static void xresize (int col, int row) {
3008 Pixmap newbuf;
3009 int oldw, oldh;
3011 if (term == NULL) return;
3012 oldw = term->picbufw;
3013 oldh = term->picbufh;
3014 term->picbufw = MAX(1, col*xw.cw);
3015 term->picbufh = MAX(1, row*xw.ch);
3016 newbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3017 XCopyArea(xw.dpy, term->picbuf, newbuf, dc.gc, 0, 0, term->picbufw, term->picbufh, 0, 0);
3018 XFreePixmap(xw.dpy, term->picbuf);
3019 XSetForeground(xw.dpy, dc.gc, dc.col[term->defbg]);
3020 if (term->picbufw > oldw) {
3021 XFillRectangle(xw.dpy, newbuf, dc.gc, oldw, 0, term->picbufw-oldw, MIN(term->picbufh, oldh));
3022 } else if (term->picbufw < oldw && (opt_border > 0 || xw.w > term->picbufw)) {
3023 XClearArea(xw.dpy, xw.win, opt_border+term->picbufw, opt_border, xw.w-term->picbufh-opt_border, opt_border+MIN(term->picbufh, oldh), False);
3025 if (term->picbufh > oldh) {
3026 XFillRectangle(xw.dpy, newbuf, dc.gc, 0, oldh, term->picbufw, term->picbufh-oldh);
3027 } else if (term->picbufh < oldh && (opt_border > 0 || xw.tabheight > 0 || xw.h > term->picbufh)) {
3028 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);
3030 term->picbuf = newbuf;
3031 tfulldirt();
3032 updateTabBar = 1;
3036 ////////////////////////////////////////////////////////////////////////////////
3037 // x11 drawing and utils
3038 static void xloadcols (void) {
3039 int f, r, g, b;
3040 XColor color;
3041 ulong white = WhitePixel(xw.dpy, xw.scr);
3043 dc.col = calloc(MAX_COLOR+1, sizeof(dc.col[0]));
3044 for (f = 0; f <= MAX_COLOR; ++f) dc.col[f] = white;
3045 /* load colors [0-15] */
3046 for (f = 0; f <= 15; ++f) {
3047 const char *cname = opt_colornames[f]!=NULL?opt_colornames[f]:defcolornames[f];
3049 if (!XAllocNamedColor(xw.dpy, xw.cmap, cname, &color, &color)) {
3050 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, cname);
3051 } else {
3052 dc.col[f] = color.pixel;
3055 /* load colors [256-...] */
3056 for (f = 256; f <= MAX_COLOR; ++f) {
3057 const char *cname = opt_colornames[f];
3059 if (cname == NULL) {
3060 if (LEN(defextcolornames) <= f-256) continue;
3061 cname = defextcolornames[f-256];
3063 if (cname == NULL) continue;
3064 if (!XAllocNamedColor(xw.dpy, xw.cmap, cname, &color, &color)) {
3065 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, cname);
3066 } else {
3067 dc.col[f] = color.pixel;
3070 /* load colors [16-255] ; same colors as xterm */
3071 for (f = 16, r = 0; r < 6; ++r) {
3072 for (g = 0; g < 6; ++g) {
3073 for (b = 0; b < 6; ++b) {
3074 if (opt_colornames[f] != NULL) {
3075 if (!XAllocNamedColor(xw.dpy, xw.cmap, opt_colornames[f], &color, &color)) {
3076 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, opt_colornames[f]);
3077 } else {
3078 dc.col[f] = color.pixel;
3080 } else {
3081 color.red = r == 0 ? 0 : 0x3737+0x2828*r;
3082 color.green = g == 0 ? 0 : 0x3737+0x2828*g;
3083 color.blue = b == 0 ? 0 : 0x3737+0x2828*b;
3084 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3085 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3086 } else {
3087 dc.col[f] = color.pixel;
3090 ++f;
3094 for (r = 0; r < 24; ++r, ++f) {
3095 if (opt_colornames[f] != NULL) {
3096 if (!XAllocNamedColor(xw.dpy, xw.cmap, opt_colornames[f], &color, &color)) {
3097 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, opt_colornames[f]);
3098 } else {
3099 dc.col[f] = color.pixel;
3101 } else {
3102 color.red = color.green = color.blue = 0x0808+0x0a0a*r;
3103 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3104 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3105 } else {
3106 dc.col[f] = color.pixel;
3111 for (int f = 0; f < LEN(opt_colornames); ++f) if (opt_colornames[f]) free(opt_colornames[f]);
3115 static void xclear (int x1, int y1, int x2, int y2) {
3116 XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? term->deffg : term->defbg]);
3117 XFillRectangle(xw.dpy, term->picbuf, dc.gc, x1*xw.cw, y1*xw.ch, (x2-x1+1)*xw.cw, (y2-y1+1)*xw.ch);
3121 static void xhints (void) {
3122 XClassHint class = {opt_class, opt_title};
3123 XWMHints wm = {.flags = InputHint, .input = 1};
3124 XSizeHints size = {
3125 .flags = PSize | PResizeInc | PBaseSize,
3126 .height = xw.h,
3127 .width = xw.w,
3128 .height_inc = xw.ch,
3129 .width_inc = xw.cw,
3130 .base_height = 2*opt_border+xw.tabheight,
3131 .base_width = 2*opt_border,
3133 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, &size, &wm, &class);
3137 static XFontSet xinitfont (const char *fontstr) {
3138 XFontSet set;
3139 char *def, **missing;
3140 int n;
3142 missing = NULL;
3143 set = XCreateFontSet(xw.dpy, fontstr, &missing, &n, &def);
3144 if (missing) {
3145 while (n--) fprintf(stderr, "sterm: missing fontset: %s\n", missing[n]);
3146 XFreeStringList(missing);
3148 return set;
3152 static void xgetfontinfo (XFontSet set, int *ascent, int *descent, short *lbearing, short *rbearing, Font *fid) {
3153 XFontStruct **xfonts;
3154 char **font_names;
3155 int n;
3157 *ascent = *descent = *lbearing = *rbearing = 0;
3158 n = XFontsOfFontSet(set, &xfonts, &font_names);
3159 for (int f = 0; f < n; ++f) {
3160 if (f == 0) *fid = (*xfonts)->fid;
3161 *ascent = MAX(*ascent, (*xfonts)->ascent);
3162 *descent = MAX(*descent, (*xfonts)->descent);
3163 *lbearing = MAX(*lbearing, (*xfonts)->min_bounds.lbearing);
3164 *rbearing = MAX(*rbearing, (*xfonts)->max_bounds.rbearing);
3165 ++xfonts;
3170 static void initfonts (const char *fontstr, const char *bfontstr, const char *tabfont) {
3171 if ((dc.font[0].set = xinitfont(fontstr)) == NULL) die("can't load font %s", fontstr);
3172 xgetfontinfo(dc.font[0].set, &dc.font[0].ascent, &dc.font[0].descent, &dc.font[0].lbearing, &dc.font[0].rbearing, &dc.font[0].fid);
3174 if ((dc.font[1].set = xinitfont(bfontstr)) == NULL) die("can't load font %s", bfontstr);
3175 xgetfontinfo(dc.font[1].set, &dc.font[1].ascent, &dc.font[1].descent, &dc.font[1].lbearing, &dc.font[1].rbearing, &dc.font[1].fid);
3177 if ((dc.font[2].set = xinitfont(tabfont)) == NULL) die("can't load font %s", tabfont);
3178 xgetfontinfo(dc.font[2].set, &dc.font[2].ascent, &dc.font[2].descent, &dc.font[2].lbearing, &dc.font[2].rbearing, &dc.font[2].fid);
3182 static void xinit (void) {
3183 XSetWindowAttributes attrs;
3184 Window parent;
3185 XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
3187 if (!(xw.dpy = XOpenDisplay(NULL))) die("can't open display");
3189 XA_VT_SELECTION = XInternAtom(xw.dpy, "_STERM_SELECTION_", 0);
3190 XA_CLIPBOARD = XInternAtom(xw.dpy, "CLIPBOARD", 0);
3191 XA_UTF8 = XInternAtom(xw.dpy, "UTF8_STRING", 0);
3192 XA_NETWM_NAME = XInternAtom(xw.dpy, "_NET_WM_NAME", 0);
3193 XA_TARGETS = XInternAtom(xw.dpy, "TARGETS", 0);
3194 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
3196 xw.scr = XDefaultScreen(xw.dpy);
3197 /* font */
3198 initfonts(opt_fontnorm, opt_fontbold, opt_fonttab);
3199 /* XXX: Assuming same size for bold font */
3200 xw.cw = dc.font[0].rbearing-dc.font[0].lbearing;
3201 xw.ch = dc.font[0].ascent+dc.font[0].descent;
3202 xw.tch = dc.font[2].ascent+dc.font[2].descent;
3203 xw.tabheight = xw.tch+2;
3204 //fprintf(stderr, "tabh: %d\n", xw.tabheight);
3205 //xw.tabheight = 0;
3206 /* colors */
3207 xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
3208 xloadcols();
3209 /* window - default size */
3210 term->picbufh = term->row*xw.ch;
3211 term->picbufw = term->col*xw.cw;
3213 xw.h = term->picbufh+2*opt_border+xw.tabheight;
3214 xw.w = term->picbufw+2*opt_border;
3216 attrs.background_pixel = dc.col[defaultBG];
3217 attrs.border_pixel = dc.col[defaultBG];
3218 attrs.bit_gravity = NorthWestGravity;
3219 attrs.event_mask = FocusChangeMask | KeyPressMask
3220 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
3221 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask
3222 | EnterWindowMask | LeaveWindowMask;
3223 attrs.colormap = xw.cmap;
3224 //fprintf(stderr, "oe: [%s]\n", opt_embed);
3225 parent = opt_embed ? strtol(opt_embed, NULL, 0) : XRootWindow(xw.dpy, xw.scr);
3226 xw.win = XCreateWindow(xw.dpy, parent, 0, 0,
3227 xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
3228 XDefaultVisual(xw.dpy, xw.scr),
3229 CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask
3230 | CWColormap,
3231 &attrs);
3232 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3233 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
3234 /* input methods */
3235 xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
3236 xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL);
3237 /* gc */
3238 dc.gc = XCreateGC(xw.dpy, xw.win, 0, NULL);
3239 /* white cursor, black outline */
3240 xw.cursor = XCreateFontCursor(xw.dpy, XC_xterm);
3241 XDefineCursor(xw.dpy, xw.win, xw.cursor);
3242 XRecolorCursor(xw.dpy, xw.cursor,
3243 &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff},
3244 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
3245 fixWindowTitle(term);
3246 //XStoreName(xw.dpy, xw.win, opt_title);
3248 XSetForeground(xw.dpy, dc.gc, 0);
3249 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
3250 if (xw.tabheight > 0) XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
3252 XMapWindow(xw.dpy, xw.win);
3253 xhints();
3255 xw.blankPtr = XCreateGlyphCursor(xw.dpy, dc.font[0].fid, dc.font[0].fid, ' ', ' ', &blackcolor, &blackcolor);
3257 XSync(xw.dpy, 0);
3261 static void xblankPointer (void) {
3262 if (!ptrBlanked && xw.blankPtr != None) {
3263 ptrBlanked = 1;
3264 XDefineCursor(xw.dpy, xw.win, xw.blankPtr);
3265 XFlush(xw.dpy);
3270 static void xunblankPointer (void) {
3271 if (ptrBlanked && xw.cursor != None) {
3272 ptrBlanked = 0;
3273 XDefineCursor(xw.dpy, xw.win, xw.cursor);
3274 XFlush(xw.dpy);
3275 ptrLastMove = mclock_ticks();
3280 static void xdraws (const char *s, const Glyph *base, int x, int y, int charlen, int bytelen) {
3281 int fg = base->fg, bg = base->bg, temp;
3282 int winx = x*xw.cw, winy = y*xw.ch+dc.font[0].ascent, width = charlen*xw.cw;
3283 XFontSet fontset = dc.font[0].set;
3284 int defF = base->mode&ATTR_DEFFG, defB = base->mode&ATTR_DEFBG;
3286 /* only switch default fg/bg if term is in RV mode */
3287 if (IS_SET(MODE_REVERSE)) {
3288 if (defF) fg = term->defbg;
3289 if (defB) bg = term->deffg;
3291 if (base->mode&ATTR_REVERSE) defF = defB = 0;
3292 if (base->mode & ATTR_BOLD) {
3293 if (defF && defB && defaultBoldFG >= 0) fg = defaultBoldFG;
3294 else if (fg < 8) fg += 8;
3295 fontset = dc.font[1].set;
3297 if (base->mode & ATTR_UNDERLINE && defaultUnderlineFG >= 0) {
3298 if (defF && defB) fg = defaultUnderlineFG;
3301 if (base->mode&ATTR_REVERSE) { temp = fg; fg = bg; bg = temp; }
3303 XSetBackground(xw.dpy, dc.gc, dc.col[bg]);
3304 XSetForeground(xw.dpy, dc.gc, dc.col[fg]);
3308 FILE *fo = fopen("zlog.log", "ab");
3309 fprintf(fo, "xdraws: x=%d; y=%d; charlen=%d; bytelen=%d\n", x, y, charlen, bytelen);
3310 fclose(fo);
3314 if (IS_GFX(base->mode)) {
3315 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
3316 } else if (!needConversion) {
3317 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
3318 } else {
3319 if (bytelen > 0) {
3320 //k8: dunno why, but Xutf8DrawImageString() ignores ascii (at least for terminus)
3321 const char *pos = s;
3322 int xpos = winx;
3324 while (pos < s+bytelen) {
3325 const char *e;
3326 int clen;
3328 if ((unsigned char)(pos[0]) < 128) {
3329 for (e = pos+1; e < s+bytelen && (unsigned char)(*e) < 128; ++e) ;
3330 clen = e-pos;
3331 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
3334 FILE *fo = fopen("zlog.log", "ab");
3335 fprintf(fo, " norm: clen=%d (%d); [", clen, (int)(e-pos));
3336 fwrite(pos, 1, e-pos, fo);
3337 fprintf(fo, "]\n");
3338 fclose(fo);
3341 } else {
3342 for (clen = 0, e = pos; e < s+bytelen && (unsigned char)(*e) >= 128; ++e) {
3343 if (((unsigned char)(e[0])&0xc0) == 0xc0) ++clen;
3345 Xutf8DrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
3348 FILE *fo = fopen("zlog.log", "ab");
3349 fprintf(fo, " utf8: clen=%d (%d); [", clen, (int)(e-pos));
3350 fwrite(pos, 1, e-pos, fo);
3351 fprintf(fo, "]\n");
3352 fclose(fo);
3356 xpos += xw.cw*clen;
3357 pos = e;
3362 if (base->mode & ATTR_UNDERLINE) {
3363 XDrawLine(xw.dpy, term->picbuf, dc.gc, winx, winy+1, winx+width-1, winy+1);
3368 /* copy buffer pixmap to screen pixmap */
3369 static void xcopy (int x, int y, int cols, int rows) {
3370 int src_x = x*xw.cw, src_y = y*xw.ch, src_w = cols*xw.cw, src_h = rows*xw.ch;
3371 int dst_x = opt_border+src_x, dst_y = opt_border+src_y;
3373 XCopyArea(xw.dpy, term->picbuf, xw.win, dc.gc, src_x, src_y, src_w, src_h, dst_x, dst_y);
3377 static void xdrawcursor (void) {
3378 int sl;
3379 int scrx, scry;
3381 if (term == NULL) return;
3383 LIMIT(term->oldcx, 0, term->col-1);
3384 LIMIT(term->oldcy, 0, term->row-1);
3386 scrx = term->oldcx;
3387 scry = term->oldcy+term->topline;
3388 if (scry < term->row &&
3389 (term->oldcy != term->c.y || term->oldcx != term->c.x || (term->c.state&CURSOR_HIDE) || !(xw.state & WIN_FOCUSED))) {
3390 /* remove the old cursor */
3391 sl = utf8size(term->line[term->oldcy][scrx].c);
3392 xdraws(term->line[term->oldcy][scrx].c, &term->line[term->oldcy][scrx], scrx, scry, 1, sl);
3393 //xclear(scrx, term->oldcy, scrx, term->oldcy);
3394 xcopy(scrx, scry, 1, 1);
3396 /* draw the new one */
3397 if (!(term->c.state&CURSOR_HIDE)) {
3398 Glyph g;
3400 scrx = term->c.x;
3401 scry = term->c.y+term->topline;
3402 if (scry < term->row) {
3403 if (!(xw.state & WIN_FOCUSED)) {
3404 if (defaultCursorInactiveBG < 0) {
3405 XSetForeground(xw.dpy, dc.gc, dc.col[defaultCursorBG]);
3406 XDrawRectangle(xw.dpy, term->picbuf, dc.gc, scrx*xw.cw, scry*xw.ch, xw.cw-1, xw.ch-1);
3407 goto done;
3409 g.bg = defaultCursorInactiveBG;
3410 g.fg = defaultCursorInactiveFG;
3411 } else {
3412 g.fg = defaultCursorFG;
3413 g.bg = defaultCursorBG;
3415 memcpy(g.c, term->line[term->c.y][scrx].c, UTF_SIZ);
3416 g.state = 0;
3417 g.mode = 0;
3418 if (IS_SET(MODE_REVERSE)) g.mode |= ATTR_REVERSE;
3419 sl = utf8size(g.c);
3420 xdraws(g.c, &g, scrx, scry, 1, sl);
3421 term->oldcx = scrx;
3422 term->oldcy = term->c.y;
3424 done:
3425 xcopy(scrx, scry, 1, 1);
3430 #define TAB_VISIBLE (6)
3432 static void xdrawTabBar (void) {
3433 if (xw.tabheight > 0 && updateTabBar) {
3434 static int tableft = 0;
3435 int tabstart;
3436 int tabw = xw.w/TAB_VISIBLE;
3437 XFontSet fontset = dc.font[2].set;
3439 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabBG]);
3440 XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
3442 if (tableft+TAB_VISIBLE-1 > termidx) tableft = termidx-TAB_VISIBLE;
3443 else if (termidx < tableft) tableft = termidx;
3444 if (tableft < 0) tableft = 0;
3445 tabstart = tableft;
3446 for (int f = tabstart; f < TAB_VISIBLE; ++f) {
3447 int x = (f-tabstart)*tabw;;
3448 const char *title;
3449 char *tit;
3451 if (f >= term_count) {
3452 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabBG]);
3453 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, xw.w, xw.tabheight);
3454 break;
3456 title = term_array[f]->title;
3457 if (!title[0]) title = opt_title;
3458 tit = SPrintf("[%d]%s", f, title);
3459 title = tit;
3461 XSetForeground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabBG : normalTabBG]);
3462 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, tabw, xw.tabheight);
3464 XSetBackground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabBG : normalTabBG]);
3465 XSetForeground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabFG : normalTabFG]);
3467 if (needConversion) {
3468 int xx = x+2;
3470 while (*title && xx < x+tabw) {
3471 const char *e = title;
3472 XRectangle r;
3474 memset(&r, 0, sizeof(r));
3476 if ((unsigned char)(*e) > 127) {
3477 while (*e && (unsigned char)(*e) > 127) ++e;
3478 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
3479 Xutf8TextExtents(fontset, title, e-title, &r, NULL);
3480 } else {
3481 while (*e && (unsigned char)(*e) <= 127) ++e;
3482 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
3483 XmbTextExtents(fontset, title, e-title, &r, NULL);
3485 title = e;
3486 xx += r.width-r.x;
3489 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
3490 } else {
3491 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
3493 free(tit);
3495 XSetForeground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabBG : normalTabBG]);
3496 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x+tabw-2, 0, 2, xw.tabheight);
3498 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabFG]);
3499 XDrawLine(xw.dpy, xw.pictab, dc.gc, x+tabw-1, 0, x+tabw-1, xw.tabheight);
3502 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabFG]);
3503 //XDrawRectangle(xw.dpy, xw.pictab, dc.gc, -1, 0, xw.w+1, xw.tabheight+1);
3504 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, 0);
3506 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, xw.h-xw.tabheight);
3507 updateTabBar = 0;
3511 static void drawline (int x1, int x2, int scry, int lineno) {
3512 int ic, ib, ox, sl;
3513 int stx, ex;
3514 Glyph base, new;
3516 //dlogf("drawline: x1=%d; x2=%d; scry=%d; row:%d; lineno=%d\n", x1, x2, scry, term->row, lineno);
3517 if (scry < 0 || scry >= term->row) {
3518 return;
3520 if (lineno < 0 || lineno >= term->linecount) {
3521 xclear(0, scry, term->col-1, scry);
3522 xcopy(0, scry, term->col, 1);
3523 } else {
3524 if (lineno < term->row && term->topline == 0) {
3525 //if (term->topline != 0) term->dirty[lineno] = 2;
3526 if (!term->dirty[lineno]) return;
3527 // fix 'dirty' flag for line
3528 if (term->dirty[lineno]&0x02) {
3529 // mark full line as dirty
3530 for (int x = 0; x < term->col; ++x) term->line[lineno][x].state |= GLYPH_DIRTY;
3532 // correct 'dirty' flag
3533 term->dirty[lineno] = 0;
3534 if (x1 > 0) for (int x = 0; x < x1; ++x) if (term->line[lineno][x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
3535 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; }
3537 // find dirty region
3538 for (stx = x1; stx < x2; ++stx) if (term->line[lineno][stx].state&GLYPH_DIRTY) break;
3539 for (ex = x2; ex > stx; --ex) if (term->line[lineno][ex-1].state&GLYPH_DIRTY) break;
3540 if (stx >= x2 || ex <= stx) return; // nothing to do
3541 //dlogf(" region: (%d,%d)\n", stx, ex);
3542 } else {
3543 term->dirty[lineno] = 0;
3544 stx = 0;
3545 ex = term->col;
3548 base = term->line[lineno][stx];
3549 ic = ib = 0;
3550 ox = stx;
3551 //xclear(stx, scry, ex-1, scry); //k8: actually, we don't need this for good monospace fonts, so...
3552 for (int x = stx; x < ex; ++x) {
3553 new = term->line[lineno][x];
3554 term->line[lineno][x].state &= ~GLYPH_DIRTY; //!
3555 if (term->sel.bx != -1 && new.c[0] && selected(x, lineno)) new.mode ^= ATTR_REVERSE;
3556 if (ib > 0 && (ATTRCMP(base, new) || ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
3557 // flush draw buffer
3558 xdraws(term->drawbuf, &base, ox, scry, ic, ib);
3559 ic = ib = 0;
3561 if (ib == 0) { ox = x; base = new; }
3562 sl = utf8size(new.c);
3563 memcpy(term->drawbuf+ib, new.c, sl);
3564 ib += sl;
3565 ++ic;
3567 if (ib > 0) xdraws(term->drawbuf, &base, ox, scry, ic, ib);
3568 //xcopy(0, scry, term->col, 1);
3569 if (term->c.y == lineno && term->c.x >= stx && term->c.x < ex) xdrawcursor();
3570 xcopy(stx, scry, ex-stx, 1);
3575 static void drawregion (int x1, int y1, int x2, int y2, int forced) {
3576 if (!forced && (xw.state&WIN_VISIBLE) == 0) {
3577 //dlogf("invisible");
3578 term->lastDrawTime = 1;
3579 term->wantRedraw = 1;
3580 return;
3583 if (term->topline < term->row) {
3584 for (int y = y1; y < y2; ++y) drawline(x1, x2, y+term->topline, y);
3586 if (term->topline > 0) {
3587 int scry = MIN(term->topline, term->row), y = term->row;
3589 if (term->topline >= term->row) y += term->topline-term->row;
3590 while (--scry >= 0) {
3591 drawline(0, term->col, scry, y);
3592 ++y;
3595 xdrawcursor();
3596 xdrawTabBar();
3597 XFlush(xw.dpy);
3598 term->lastDrawTime = mclock_ticks();
3599 term->wantRedraw = 0;
3603 static void draw (int forced) {
3604 if (term != NULL) {
3605 //dlogf("draw(%d)\n", forced);
3606 drawregion(0, 0, term->col, term->row, forced);
3611 static void expose (XEvent *ev) {
3612 XExposeEvent *e = &ev->xexpose;
3614 if (xw.state&WIN_REDRAW) {
3615 if (!e->count && term != NULL) {
3616 xw.state &= ~WIN_REDRAW;
3617 xcopy(0, 0, term->col, term->row);
3619 } else if (term != NULL) {
3620 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);
3622 xdrawTabBar();
3623 //XFlush(xw.dpy);
3627 static void visibility (XEvent *ev) {
3628 XVisibilityEvent *e = &ev->xvisibility;
3630 if (e->state == VisibilityFullyObscured) xw.state &= ~WIN_VISIBLE;
3631 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 */
3635 static void unmap (XEvent *ev) {
3636 xw.state &= ~WIN_VISIBLE;
3640 static void xseturgency (int add) {
3641 XWMHints *h = XGetWMHints(xw.dpy, xw.win);
3643 h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
3644 XSetWMHints(xw.dpy, xw.win, h);
3645 XFree(h);
3649 static void focus (XEvent *ev) {
3650 if (ev->type == FocusIn) {
3651 xw.state |= WIN_FOCUSED;
3652 xseturgency(0);
3653 } else {
3654 xw.state &= ~WIN_FOCUSED;
3656 //draw(1);
3657 xdrawcursor();
3658 xdrawTabBar();
3659 xcopy(0, 0, term->col, term->row);
3663 ////////////////////////////////////////////////////////////////////////////////
3664 // keyboard mapping
3665 static const char *kmap (KeySym k, uint state) {
3666 const char *res = NULL;
3667 state &= ~Mod2Mask; // numlock
3668 for (int f = 0; f < keymap_used; ++f) {
3669 uint mask = keymap[f].mask;
3671 if (keymap[f].key == k && ((state&mask) == mask || (mask == XK_NO_MOD && !state))) {
3672 //fprintf(stderr, "kp: %d\n", IS_SET(MODE_APPKEYPAD));
3673 if (!IS_SET(MODE_APPKEYPAD)) {
3674 if (!keymap[f].kp) {
3675 //fprintf(stderr, "nokp! %d %s\n", keymap[f].kp, keymap[f].str+1);
3676 return keymap[f].str; // non-keypad hit
3678 continue;
3680 //fprintf(stderr, "kp! %d %s\n", keymap[f].kp, keymap[f].str+1);
3681 if (keymap[f].kp) return keymap[f].str; // keypad hit
3682 res = keymap[f].str; // kp mode, but non-kp mapping found
3685 return res;
3689 static const char *kbind (KeySym k, uint state) {
3690 state &= ~Mod2Mask; // numlock
3691 for (int f = 0; f < keybinds_used; ++f) {
3692 uint mask = keybinds[f].mask;
3694 if (keybinds[f].key == k && ((state&mask) == mask || (mask == XK_NO_MOD && !state))) return keybinds[f].str;
3696 return NULL;
3700 static KeySym do_keytrans (KeySym ks) {
3701 for (int f = 0; f < keytrans_used; ++f) if (keytrans[f].src == ks) return keytrans[f].dst;
3702 return ks;
3706 static void kpress (XEvent *ev) {
3707 XKeyEvent *e = &ev->xkey;
3708 KeySym ksym = NoSymbol;
3709 const char *kstr;
3710 int len;
3711 Status status;
3712 char buf[32];
3714 if (term == NULL) return;
3715 //if (len < 1) len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
3716 if ((len = Xutf8LookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status)) > 0) buf[len] = 0;
3717 // leave only known mods
3718 e->state &= (Mod1Mask | Mod4Mask | ControlMask | ShiftMask);
3719 #ifdef DUMP_KEYSYMS
3721 const char *ksname = XKeysymToString(ksym);
3723 fprintf(stderr, "utf(%d):[%s] (%s) 0x%08x\n", len, len>=0?buf:"<shit>", ksname, (unsigned int)e->state);
3725 #endif
3726 if ((kstr = kbind(ksym, e->state)) != NULL) {
3727 // keybind found
3728 if (term->topline != 0) {
3729 term->topline = 0;
3730 term->wantRedraw = 0;
3731 term->lastDrawTime = 0;
3733 executeCommands(kstr);
3734 return;
3737 if ((kstr = kmap(do_keytrans(ksym), e->state)) != NULL) {
3738 if (kstr[0]) ttywritestr(kstr);
3739 } else {
3740 int meta = (e->state&Mod1Mask);
3742 int shift = (e->state&ShiftMask);
3743 int ctrl = (e->state&ControlMask);
3745 switch (ksym) {
3746 case XK_Return:
3747 if (meta) {
3748 ttywritestr("\x1b\x0a");
3749 } else {
3750 if (IS_SET(MODE_CRLF)) ttywritestr("\r\n"); else ttywritestr("\r");
3752 break;
3753 default:
3754 if (len > 0) {
3755 if (meta && len == 1) ttywritestr("\x1b");
3756 ttywrite(buf, len);
3758 break;
3764 ////////////////////////////////////////////////////////////////////////////////
3765 // xembed?
3766 static void cmessage (XEvent *e) {
3767 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
3768 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
3769 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
3770 xw.state |= WIN_FOCUSED;
3771 xseturgency(0);
3772 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
3773 xw.state &= ~WIN_FOCUSED;
3775 draw(1);
3780 ////////////////////////////////////////////////////////////////////////////////
3781 static void resize (XEvent *e) {
3782 int col, row;
3783 Term *ot = term;
3785 if (e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) return;
3786 xw.w = e->xconfigure.width;
3787 xw.h = e->xconfigure.height;
3788 col = (xw.w-2*opt_border)/xw.cw;
3789 row = (xw.h-2*opt_border-xw.tabheight)/xw.ch;
3790 if (col == term->col && row == term->row) return;
3791 for (int f = 0; f < term_count; ++f) {
3792 term = term_array[f];
3793 if (tresize(col, row) && ot == term) draw(1);
3794 ttyresize();
3795 xresize(col, row);
3797 term = ot;
3798 XFreePixmap(xw.dpy, xw.pictab);
3799 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
3800 updateTabBar = 1;
3804 static inline int last_draw_too_old (void) {
3805 return (mclock_ticks()-term->lastDrawTime >= DRAW_TIMEOUT);
3809 ////////////////////////////////////////////////////////////////////////////////
3810 // main loop
3811 static void (*handler[LASTEvent])(XEvent *) = {
3812 [KeyPress] = kpress,
3813 [ClientMessage] = cmessage,
3814 [ConfigureNotify] = resize,
3815 [VisibilityNotify] = visibility,
3816 [UnmapNotify] = unmap,
3817 [Expose] = expose,
3818 [FocusIn] = focus,
3819 [FocusOut] = focus,
3820 [MotionNotify] = bmotion,
3821 [ButtonPress] = bpress,
3822 [ButtonRelease] = brelease,
3823 [SelectionNotify] = selnotify,
3824 [SelectionRequest] = selrequest,
3825 [SelectionClear] = selclear,
3829 static void run (void) {
3830 //int stuff_to_print = 0;
3831 int xfd = XConnectionNumber(xw.dpy);
3833 ptrLastMove = mclock_ticks();
3834 while (term_count > 0) {
3835 XEvent ev;
3836 fd_set rfd, wfd;
3837 struct timeval timeout;
3838 int maxfd = xfd;
3839 Term *ot;
3841 FD_ZERO(&rfd);
3842 FD_ZERO(&wfd);
3843 FD_SET(xfd, &rfd);
3844 //FD_SET(term->cmdfd, &rfd);
3845 // have something to write?
3846 for (int f = 0; f < term_count; ++f) {
3847 Term *t = term_array[f];
3849 if (!t->dead && term->cmdfd >= 0 && t->pid != 0) {
3850 if (t->cmdfd > maxfd) maxfd = t->cmdfd;
3851 FD_SET(t->cmdfd, &rfd);
3852 if (t->wrbufpos < t->wrbufused) FD_SET(t->cmdfd, &wfd);
3856 timeout.tv_sec = 0;
3857 timeout.tv_usec = SELECT_TIMEOUT*1000;
3858 if (select(maxfd+1, &rfd, &wfd, NULL, &timeout) < 0) {
3859 if (errno == EINTR) continue;
3860 die("select failed: %s", SERRNO);
3863 ot = term;
3864 for (int f = 0; f < term_count; ++f) {
3865 Term *t = term_array[f];
3867 if (!t->dead && term->cmdfd >= 0 && term->pid != 0) {
3868 term = t;
3869 if (FD_ISSET(t->cmdfd, &wfd)) ttyflushwrbuf();
3870 if (FD_ISSET(t->cmdfd, &rfd)) ttyread(); //t->wantRedraw = 1;
3871 term = ot;
3875 termcleanup();
3876 if (term_count == 0) break;
3878 if (updateTabBar || (term != NULL && !term->dead && term->wantRedraw && last_draw_too_old())) {
3879 draw(0);
3882 if (XPending(xw.dpy)) {
3883 while (XPending(xw.dpy)) {
3884 XNextEvent(xw.dpy, &ev);
3885 if (XFilterEvent(&ev, xw.win)) continue;
3886 if (handler[ev.type]) (handler[ev.type])(&ev);
3890 if (opt_ptrblank > 0 && mclock_ticks()-ptrLastMove >= opt_ptrblank) {
3891 xblankPointer();
3897 ////////////////////////////////////////////////////////////////////////////////
3898 typedef const char * (*IniHandlerFn) (const char *optname, const char *fmt, char *argstr, void *udata);
3901 typedef struct {
3902 const char *name;
3903 const char *fmt;
3904 void *udata;
3905 IniHandlerFn fn;
3906 } IniCommand;
3908 static const char *inifnGenericOneArg (const char *optname, const char *fmt, char *argstr, void *udata) {
3909 return iniParseArguments(argstr, fmt, udata);
3913 static const char *inifnGenericOneStr (const char *optname, const char *fmt, char *argstr, void *udata) {
3914 char *s = NULL;
3915 const char *err = iniParseArguments(argstr, fmt, &s);
3917 if (err != NULL) return err;
3918 if ((s = strdup(s)) == NULL) return "out of memory";
3919 if (udata) {
3920 char **ustr = (char **)udata;
3922 if (*ustr) free(*ustr);
3923 *ustr = s;
3925 return NULL;
3929 static const IniCommand iniCommands[] = {
3930 {"term", "s!-", &opt_term, inifnGenericOneStr},
3931 {"class", "s!-", &opt_class, inifnGenericOneStr},
3932 {"title", "s!-", &opt_title, inifnGenericOneStr},
3933 {"fontnorm", "s!-", &opt_fontnorm, inifnGenericOneStr},
3934 {"fontbold", "s!-", &opt_fontbold, inifnGenericOneStr},
3935 {"fonttab", "s!-", &opt_fonttab, inifnGenericOneStr},
3936 {"shell", "s!-", &opt_shell, inifnGenericOneStr},
3937 {"doubleclick_timeout", "i{0,10000}", &opt_doubleclick_timeout, inifnGenericOneArg},
3938 {"tripleclick_timeout", "i{0,10000}", &opt_tripleclick_timeout, inifnGenericOneArg},
3939 {"tabsize", "i{1,256}", &opt_tabsize, inifnGenericOneArg},
3940 {"border", "i{0,256}", &opt_border, inifnGenericOneArg},
3941 {"defaultfg", "i{0,511}", &defaultFG, inifnGenericOneArg},
3942 {"defaultbg", "i{0,511}", &defaultBG, inifnGenericOneArg},
3943 {"defaultcursorfg", "i{0,511}", &defaultCursorFG, inifnGenericOneArg},
3944 {"defaultcursorbg", "i{0,511}", &defaultCursorBG, inifnGenericOneArg},
3945 {"defaultinactivecursorfg", "i{0,511}", &defaultCursorInactiveFG, inifnGenericOneArg},
3946 {"defaultinactivecursorbg", "i{-1,511}", &defaultCursorInactiveBG, inifnGenericOneArg},
3947 {"defaultboldfg", "i{-1,511}", &defaultBoldFG, inifnGenericOneArg},
3948 {"defaultunderlinefg", "i{-1,511}", &defaultUnderlineFG, inifnGenericOneArg},
3949 {"normaltabfg", "i{0,511}", &normalTabFG, inifnGenericOneArg},
3950 {"normaltabbg", "i{0,511}", &normalTabBG, inifnGenericOneArg},
3951 {"activetabfg", "i{0,511}", &activeTabFG, inifnGenericOneArg},
3952 {"activetabbg", "i{0,511}", &activeTabBG, inifnGenericOneArg},
3953 {"maxhistory", "i{0,65535}", &opt_maxhistory, inifnGenericOneArg},
3954 {"ptrblank", "i{0,65535}", &opt_ptrblank, inifnGenericOneArg},
3955 {NULL, NULL, NULL, NULL}
3959 #define INI_LINE_SIZE (32768)
3961 // <0: file not found
3962 // >0: file loading error
3963 // 0: ok
3964 static int loadConfig (const char *fname) {
3965 int inifelse = 0; // 0: not; 1: doing true; 2: doing false; -1: waiting else/endif; -2: waiting endif
3966 FILE *fi = fopen(fname, "r");
3967 const char *err = NULL;
3968 char *line;
3969 int lineno = 0;
3971 if (fi == NULL) return -1;
3972 if ((line = malloc(INI_LINE_SIZE)) == NULL) { err = "out of memory"; goto quit; }
3974 while (fgets(line, INI_LINE_SIZE-1, fi) != NULL) {
3975 char *optname, *argstr;
3976 int goodoption = 0;
3978 ++lineno;
3979 line[INI_LINE_SIZE-1] = 0;
3980 // get option name
3981 for (optname = line; *optname && isspace(*optname); ++optname) ;
3982 if (!optname[0] || optname[0] == '#') continue; // comment
3983 if (!isalnum(optname[0])) { err = "invalid option name"; goto quit; }
3984 argstr = optname;
3985 while (*argstr) {
3986 if (!argstr[0] || isspace(argstr[0])) break;
3987 if (!isalnum(argstr[0]) && argstr[0] != '_' && argstr[0] != '.') { err = "invalid option name"; goto quit; }
3988 *argstr = tolower(*argstr);
3989 ++argstr;
3991 if (*argstr) *argstr++ = 0;
3993 if (strcasecmp(optname, "ifterm") == 0) {
3994 char *val;
3996 if (inifelse != 0) { err = "nested ifs are not allowed"; goto quit; }
3997 inifelse = -1;
3998 if ((err = iniParseArguments(argstr, "s", &val)) != NULL) goto quit;
3999 if (strcasecmp(opt_term, val) == 0) inifelse = 1;
4000 continue;
4002 if (strcasecmp(optname, "else") == 0) {
4003 switch (inifelse) {
4004 case -1: inifelse = 2; break;
4005 case 2: case -2: case 0: err = "else without if"; goto quit;
4006 case 1: inifelse = -2; break;
4008 continue;
4010 if (strcasecmp(optname, "endif") == 0) {
4011 switch (inifelse) {
4012 case -1: case -2: case 1: case 2: inifelse = 0; break;
4013 case 0: err = "endif without if"; goto quit;
4015 continue;
4018 if (inifelse < 0) {
4019 //trimstr(argstr);
4020 //fprintf(stderr, "skip: [%s]\n", argstr);
4021 continue;
4023 if (opt_term_locked && strcasecmp(optname, "term") == 0) continue; // termname given in command line
4024 // ok, we have option name in `optname` and arguments in `argstr`
4025 if (strncmp(optname, "color.", 6) == 0) {
4026 int n = 0;
4027 char *s = NULL;
4029 optname += 6;
4030 if (!optname[0]) { err = "invalid color option"; goto quit; }
4031 while (*optname) {
4032 if (!isdigit(*optname)) { err = "invalid color option"; goto quit; }
4033 n = (n*10)+(optname[0]-'0');
4034 ++optname;
4036 if (n < 0 || n > 511) { err = "invalid color index"; goto quit; }
4038 if ((err = iniParseArguments(argstr, "s!-", &s)) != NULL) goto quit;
4039 if ((s = strdup(s)) == NULL) { err = "out of memory"; goto quit; }
4040 if (opt_colornames[n] != NULL) free(opt_colornames[n]);
4041 opt_colornames[n] = s;
4042 continue;
4045 if (strcmp(optname, "unimap") == 0) {
4046 int uni, ch;
4047 char *alt = NULL;
4049 //unimap 0x2592 0x61 alt
4050 if ((err = iniParseArguments(argstr, "i{0,65535}i{0,126}|s!-", &uni, &ch, &alt)) != NULL) goto quit;
4051 if (alt != NULL && strcasecmp(alt, "alt") != 0) { err = "invalid unimap"; goto quit; }
4052 if (unimap == NULL) {
4053 if ((unimap = calloc(65536, sizeof(unimap[0]))) == NULL) { err = "out of memory"; goto quit; }
4055 if (alt != NULL && ch == 0) alt = NULL;
4056 if (alt != NULL && ch < 96) { err = "invalid unimap"; goto quit; }
4057 unimap[uni] = ch;
4058 if (alt != NULL) unimap[uni] |= 0x80;
4059 continue;
4062 if (strcmp(optname, "keytrans_reset") == 0) {
4063 if ((err = iniParseArguments(argstr, "")) != NULL) goto quit;
4064 keytrans_reset();
4065 continue;
4067 if (strcmp(optname, "keytrans") == 0) {
4068 char *src = NULL, *dst = NULL;
4070 if ((err = iniParseArguments(argstr, "s!-s!-", &src, &dst)) != NULL) goto quit;
4071 keytrans_add(src, dst);
4072 continue;
4075 if (strcmp(optname, "keybind_reset") == 0) {
4076 if ((err = iniParseArguments(argstr, "")) != NULL) goto quit;
4077 keybinds_reset();
4078 continue;
4080 if (strcmp(optname, "keybind") == 0) {
4081 char *key = NULL, *act = NULL;
4083 if ((err = iniParseArguments(argstr, "s!-R!", &key, &act)) != NULL) goto quit;
4084 keybind_add(key, act);
4085 continue;
4088 if (strcmp(optname, "keymap_reset") == 0) {
4089 if ((err = iniParseArguments(argstr, "")) != NULL) goto quit;
4090 keymap_reset();
4091 continue;
4093 if (strcmp(optname, "keymap") == 0) {
4094 char *key = NULL, *str = NULL;
4096 if ((err = iniParseArguments(argstr, "s!-s!-", &key, &str)) != NULL) goto quit;
4097 keymap_add(key, str);
4098 continue;
4101 for (int f = 0; iniCommands[f].name != NULL; ++f) {
4102 if (strcmp(iniCommands[f].name, optname) == 0) {
4103 if ((err = iniCommands[f].fn(optname, iniCommands[f].fmt, argstr, iniCommands[f].udata)) != NULL) goto quit;
4104 goodoption = 1;
4105 break;
4108 if (!goodoption) { err = "unknown option"; goto quit; }
4110 quit:
4111 if (line != NULL) free(line);
4112 fclose(fi);
4113 if (err == NULL && inifelse != 0) err = "if without endif";
4114 if (err != NULL) die("ini error at line %d: %s", lineno, err);
4115 return 0;
4119 static void initDefaultOptions (void) {
4120 opt_title = strdup("sterm");
4121 opt_class = strdup("sterm");
4122 opt_term = strdup(TNAME);
4123 opt_fontnorm = strdup(FONT);
4124 opt_fontbold = strdup(BOLDFONT);
4125 opt_fonttab = strdup(FONTTAB);
4126 opt_shell = strdup(SHELL);
4128 memset(opt_colornames, 0, sizeof(opt_colornames));
4129 for (int f = 0; f < LEN(defcolornames); ++f) opt_colornames[f] = strdup(defcolornames[f]);
4130 for (int f = 0; f < LEN(defextcolornames); ++f) opt_colornames[f+256] = strdup(defextcolornames[f]);
4132 keytrans_add("KP_Home", "Home");
4133 keytrans_add("KP_Left", "Left");
4134 keytrans_add("KP_Up", "Up");
4135 keytrans_add("KP_Right", "Right");
4136 keytrans_add("KP_Down", "Down");
4137 keytrans_add("KP_Prior", "Prior");
4138 keytrans_add("KP_Next", "Next");
4139 keytrans_add("KP_End", "End");
4140 keytrans_add("KP_Begin", "Begin");
4141 keytrans_add("KP_Insert", "Insert");
4142 keytrans_add("KP_Delete", "Delete");
4144 keybind_add("shift+Insert", "PastePrimary");
4145 keybind_add("alt+Insert", "PasteSecondary");
4147 keymap_add("BackSpace", "\177");
4148 keymap_add("Insert", "\x1b[2~");
4149 keymap_add("Delete", "\x1b[3~");
4150 keymap_add("Home", "\x1b[1~");
4151 keymap_add("End", "\x1b[4~");
4152 keymap_add("Prior", "\x1b[5~");
4153 keymap_add("Next", "\x1b[6~");
4154 keymap_add("F1", "\x1bOP");
4155 keymap_add("F2", "\x1bOQ");
4156 keymap_add("F3", "\x1bOR");
4157 keymap_add("F4", "\x1bOS");
4158 keymap_add("F5", "\x1b[15~");
4159 keymap_add("F6", "\x1b[17~");
4160 keymap_add("F7", "\x1b[18~");
4161 keymap_add("F8", "\x1b[19~");
4162 keymap_add("F9", "\x1b[20~");
4163 keymap_add("F10", "\x1b[21~");
4164 keymap_add("Up", "\x1bOA");
4165 keymap_add("Down", "\x1bOB");
4166 keymap_add("Right", "\x1bOC");
4167 keymap_add("Left", "\x1bOD");
4168 keymap_add("kpad+Up", "\x1bOA");
4169 keymap_add("kpad+Down", "\x1bOB");
4170 keymap_add("kpad+Right", "\x1bOC");
4171 keymap_add("kpad+Left", "\x1bOD");
4175 ////////////////////////////////////////////////////////////////////////////////
4176 static Term *oldTerm;
4177 static int oldTermIdx;
4178 static Term *newTerm;
4179 static int newTermIdx;
4180 static int newTermSwitch;
4183 typedef void (*CmdHandlerFn) (const char *cmdname, char *argstr);
4185 typedef struct {
4186 const char *name;
4187 CmdHandlerFn fn;
4188 } Command;
4191 static void cmdPastePrimary (const char *cmdname, char *argstr) {
4192 selpaste(XA_PRIMARY);
4196 static void cmdPasteSecondary (const char *cmdname, char *argstr) {
4197 selpaste(XA_SECONDARY);
4201 static void cmdPasteClipboard (const char *cmdname, char *argstr) {
4202 selpaste(XA_CLIPBOARD);
4206 static void cmdExec (const char *cmdname, char *argstr) {
4207 if (term->execcmd != NULL) free(term->execcmd);
4208 if (argstr[0]) {
4209 term->execcmd = strdup(argstr);
4210 } else {
4211 term->execcmd = NULL;
4216 static int parseTabArgs (char *argstr, int *noswitch, int nowrap, int idx) {
4217 for (;;) {
4218 char *arg;
4220 while (*argstr && isspace(*argstr)) ++argstr;
4221 if (!argstr[0]) break;
4222 if (iniParseArguments(argstr, "s-R-", &arg, &argstr) != NULL) break;
4224 if (strcasecmp(arg, "noswitch") == 0) *noswitch = 1;
4225 else if (strcasecmp(arg, "switch") == 0) *noswitch = 0;
4226 else if (strcasecmp(arg, "nowrap") == 0) nowrap = 1;
4227 else if (strcasecmp(arg, "wrap") == 0) nowrap = 0;
4228 else if (strcasecmp(arg, "first") == 0) idx = 0;
4229 else if (strcasecmp(arg, "last") == 0) idx = term_count-1;
4230 else if (strcasecmp(arg, "prev") == 0) idx = -1;
4231 else if (strcasecmp(arg, "next") == 0) idx = -2;
4232 else {
4233 long int n = -1;
4234 char *eptr;
4236 n = strtol(arg, &eptr, 0);
4237 if (!eptr[0] && n >= 0 && n < term_count) idx = n;
4240 switch (idx) {
4241 case -1: // prev
4242 if ((idx = termidx-1) < 0) idx = nowrap ? 0 : term_count-1;
4243 break;
4244 case -2: // next
4245 if ((idx = termidx+1) >= term_count) idx = nowrap ? term_count-1 : 0;
4246 break;
4248 return idx;
4252 static void flushNewTerm (void) {
4253 if (newTerm != NULL) {
4254 term = newTerm;
4255 termidx = newTermIdx;
4256 tinitialize(term_array[0]->col, term_array[0]->row);
4258 term->picbufh = term->row*xw.ch;
4259 term->picbufw = term->col*xw.cw;
4260 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
4261 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
4263 if (ttynew(term) != 0) {
4264 term = oldTerm;
4265 termidx = oldTermIdx;
4266 termfree(newTermIdx);
4267 } else {
4268 selinit();
4269 ttyresize();
4270 if (newTermSwitch) {
4271 term = NULL;
4272 termidx = 0;
4273 switchToTerm(newTermIdx, 1);
4274 oldTerm = term;
4275 oldTermIdx = termidx;
4276 } else {
4277 term = oldTerm;
4278 termidx = oldTermIdx;
4281 newTerm = NULL;
4286 static void cmdNewTab (const char *cmdname, char *argstr) {
4287 int noswitch = 0, idx;
4289 flushNewTerm();
4290 if ((newTerm = termalloc()) == NULL) return;
4291 /*idx =*/ parseTabArgs(argstr, &noswitch, 0, termidx);
4292 idx = term_count-1;
4293 if (!noswitch) oldTermIdx = idx;
4294 newTermIdx = termidx = idx;
4295 term = newTerm;
4296 newTermSwitch = !noswitch;
4300 static void cmdCloseTab (const char *cmdname, char *argstr) {
4301 flushNewTerm();
4302 if (!term->dead) kill(term->pid, SIGTERM);
4306 static void cmdKillTab (const char *cmdname, char *argstr) {
4307 flushNewTerm();
4308 if (!term->dead) kill(term->pid, SIGKILL);
4312 static void cmdSwitchToTab (const char *cmdname, char *argstr) {
4313 int noswitch = 0;
4315 flushNewTerm();
4316 switchToTerm(parseTabArgs(argstr, &noswitch, 0, -2), 1);
4317 oldTerm = term;
4318 oldTermIdx = termidx;
4322 static void cmdMoveTabTo (const char *cmdname, char *argstr) {
4323 int noswitch = 0, idx;
4325 flushNewTerm();
4326 idx = parseTabArgs(argstr, &noswitch, 0, termidx);
4327 if (idx != termidx && idx >= 0 && idx < term_count) {
4328 Term *t = term_array[termidx];
4330 // remove current term
4331 for (int f = termidx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
4332 // insert term
4333 for (int f = term_count-2; f >= idx; --f) term_array[f+1] = term_array[f];
4334 term_array[idx] = t;
4335 termidx = idx;
4336 oldTerm = t;
4337 oldTermIdx = idx;
4338 updateTabBar = 1;
4343 static void cmdDefaultFG (const char *cmdname, char *argstr) {
4344 int c;
4346 if (iniParseArguments(argstr, "i{0,511}", &c) == NULL) term->deffg = c;
4350 static void cmdDefaultBG (const char *cmdname, char *argstr) {
4351 int c;
4353 if (iniParseArguments(argstr, "i{0,511}", &c) == NULL) term->defbg = c;
4357 static void scrollHistory (int delta) {
4358 if (term->maxhistory < 1) return; // no history
4359 term->topline += delta;
4360 if (term->topline > term->maxhistory-term->row) term->topline = term->maxhistory-term->row;
4361 if (term->topline < 0) term->topline = 0;
4362 tfulldirt();
4363 draw(1);
4367 static void cmdScrollHistoryLineUp (const char *cmdname, char *argstr) {
4368 scrollHistory(1);
4372 static void cmdScrollHistoryPageUp (const char *cmdname, char *argstr) {
4373 scrollHistory(term->row);
4377 static void cmdScrollHistoryLineDown (const char *cmdname, char *argstr) {
4378 scrollHistory(-1);
4382 static void cmdScrollHistoryPageDown (const char *cmdname, char *argstr) {
4383 scrollHistory(-term->row);
4387 static void cmdScrollHistoryTop (const char *cmdname, char *argstr) {
4388 scrollHistory(term->linecount);
4392 static void cmdScrollHistoryBottom (const char *cmdname, char *argstr) {
4393 scrollHistory(-term->linecount);
4397 static const Command commandList[] = {
4398 {"PastePrimary", cmdPastePrimary},
4399 {"PasteSecondary", cmdPasteSecondary},
4400 {"PasteClipboard", cmdPasteClipboard},
4401 {"exec", cmdExec},
4402 {"NewTab", cmdNewTab}, // 'noswitch' 'next' 'prev' 'first' 'last'
4403 {"CloseTab", cmdCloseTab},
4404 {"KillTab", cmdKillTab},
4405 {"SwitchToTab", cmdSwitchToTab}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
4406 {"MoveTabTo", cmdMoveTabTo}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
4407 {"defaultfg", cmdDefaultFG},
4408 {"defaultbg", cmdDefaultBG},
4409 {"ScrollHistoryLineUp", cmdScrollHistoryLineUp},
4410 {"ScrollHistoryPageUp", cmdScrollHistoryPageUp},
4411 {"ScrollHistoryLineDown", cmdScrollHistoryLineDown},
4412 {"ScrollHistoryPageDown", cmdScrollHistoryPageDown},
4413 {"ScrollHistoryTop", cmdScrollHistoryTop},
4414 {"ScrollHistoryBottom", cmdScrollHistoryBottom},
4416 {"term", cmdTermName},
4417 {"title", cmdWinTitle},
4418 {"tabsize", cmdTabSize},
4419 {"defaultcursorfg", cmdDefaultCursorFG},
4420 {"defaultcursorbg", cmdDefaultCursorBG},
4421 {"defaultinactivecursorfg", cmdDefaultInactiveCursorFG},
4422 {"defaultinactivecursorbg", cmdDefaultInactiveCursorBG},
4423 {"defaultboldfg", cmdDefaultBoldFG},
4424 {"defaultunderlinefg", cmdDefaultUnderlineFG},
4426 {NULL, NULL}
4430 static void executeCommand (const char *str, int slen) {
4431 const char *e;
4432 char *cmdname;
4434 if (str == NULL) return;
4435 if (slen < 0) slen = strlen(str);
4437 for (int f = 0; f < slen; ++f) if (!str[f]) { slen = f; break; }
4439 while (slen > 0 && isspace(*str)) { ++str; --slen; }
4440 if (slen < 1 || !str[0]) return;
4442 for (e = str; slen > 0 && !isspace(*e); ++e, --slen) ;
4444 if (e-str > 127) return;
4445 cmdname = alloca(e-str+8);
4446 if (cmdname == NULL) return;
4447 memcpy(cmdname, str, e-str);
4448 cmdname[e-str] = 0;
4449 while (slen > 0 && isspace(*e)) { ++e; --slen; }
4451 for (int f = 0; commandList[f].name != NULL; ++f) {
4452 if (strcasecmp(commandList[f].name, cmdname) == 0) {
4453 char *left = calloc(slen+2, 1);
4455 if (left != NULL) {
4456 if (slen > 0) memcpy(left, e, slen);
4457 //fprintf(stderr, "command: [%s]; args: [%s]\n", cmdname, left);
4458 commandList[f].fn(cmdname, left);
4459 free(left);
4461 break;
4467 static void executeCommands (const char *str) {
4468 oldTerm = term;
4469 oldTermIdx = termidx;
4470 newTerm = NULL;
4471 newTermSwitch = 0;
4472 if (str == NULL) return;
4473 while (*str) {
4474 const char *ce;
4475 char qch;
4477 while (*str && isspace(*str)) ++str;
4478 if (!*str) break;
4479 if (*str == ';') { ++str; continue; }
4481 ce = str;
4482 qch = ' ';
4483 while (*ce) {
4484 if (*ce == ';' && qch == ' ') break;
4485 if (qch != ' ' && *ce == qch) { qch = ' '; ++ce; continue; }
4486 if (*ce == '"' || *ce == '\'') {
4487 if (qch == ' ') qch = *ce;
4488 ++ce;
4489 continue;
4491 if (*ce++ == '\\' && *ce) ++ce;
4494 executeCommand(str, ce-str);
4495 if (*ce) str = ce+1; else break;
4497 flushNewTerm();
4498 switchToTerm(oldTermIdx, 1);
4502 ////////////////////////////////////////////////////////////////////////////////
4503 int main (int argc, char *argv[]) {
4504 char *configfile = NULL;
4506 //dbgLogInit();
4508 for (int f = 1; f < argc; f++) {
4509 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
4510 if (strcmp(argv[f], "-into") == 0) { ++f; continue; }
4511 if (strcmp(argv[f], "-embed") == 0) { ++f; continue; }
4512 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
4513 case 'e': f = argc+1; break;
4514 case 't':
4515 case 'c':
4516 case 'w':
4517 case 'b':
4518 ++f;
4519 break;
4520 case 'T':
4521 if (++f < argc) {
4522 free(opt_term);
4523 opt_term = strdup(argv[f]);
4524 opt_term_locked = 1;
4526 break;
4527 case 'C':
4528 if (++f < argc) {
4529 if (configfile) free(configfile);
4530 configfile = strdup(argv[f]);
4532 break;
4533 case 'v':
4534 case 'h':
4535 default:
4536 fprintf(stderr, "%s", USAGE);
4537 exit(EXIT_FAILURE);
4541 initDefaultOptions();
4542 if (configfile == NULL) {
4543 const char *home = getenv("HOME");
4545 if (home != NULL) {
4546 configfile = SPrintf("%s/.sterm.rc", home);
4547 if (loadConfig(configfile) == 0) goto cfgdone;
4548 free(configfile); configfile = NULL;
4550 configfile = SPrintf("%s/.config/sterm.rc", home);
4551 if (loadConfig(configfile) == 0) goto cfgdone;
4552 free(configfile); configfile = NULL;
4555 configfile = SPrintf("/etc/sterm.rc");
4556 if (loadConfig(configfile) == 0) goto cfgdone;
4557 free(configfile); configfile = NULL;
4559 configfile = SPrintf("/etc/sterm/sterm.rc");
4560 if (loadConfig(configfile) == 0) goto cfgdone;
4561 free(configfile); configfile = NULL;
4563 configfile = SPrintf("./.sterm.rc");
4564 if (loadConfig(configfile) == 0) goto cfgdone;
4565 free(configfile); configfile = NULL;
4566 // no config
4567 } else {
4568 if (loadConfig(configfile) < 0) die("config file '%s' not found!", configfile);
4570 cfgdone:
4571 if (configfile != NULL) free(configfile);
4573 for (int f = 1; f < argc; f++) {
4574 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
4575 if (strcmp(argv[f], "-into") == 0 || strcmp(argv[f], "-embed") == 0) {
4576 if (opt_embed) free(opt_embed);
4577 opt_embed = strdup(argv[f]);
4578 continue;
4580 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
4581 case 't':
4582 if (++f < argc) {
4583 free(opt_title);
4584 opt_title = strdup(argv[f]);
4586 break;
4587 case 'c':
4588 if (++f < argc) {
4589 free(opt_class);
4590 opt_class = strdup(argv[f]);
4592 break;
4593 case 'w':
4594 if (++f < argc) {
4595 if (opt_embed) free(opt_embed);
4596 opt_embed = strdup(argv[f]);
4598 break;
4599 case 'e':
4600 /* eat every remaining arguments */
4601 if (++f < argc) opt_cmd = &argv[f];
4602 f = argc+1;
4603 case 'T':
4604 ++f;
4605 break;
4606 case 'C':
4607 if (++f < argc) {
4608 if (configfile) free(configfile);
4609 configfile = strdup(argv[f]);
4611 break;
4612 case 'v':
4613 case 'h':
4614 default:
4615 fprintf(stderr, "%s", USAGE);
4616 exit(EXIT_FAILURE);
4620 setenv("TERM", opt_term, 1);
4621 mclock_init();
4622 setlocale(LC_ALL, "");
4623 initLCConversion();
4624 updateTabBar = 1;
4625 termidx = 0;
4626 term = termalloc();
4627 if (term->execcmd != NULL) { free(term->execcmd); term->execcmd = NULL; }
4628 tinitialize(80, 25);
4629 if (ttynew(term) != 0) die("can't run process");
4630 opt_cmd = NULL;
4631 xinit();
4632 selinit();
4633 run();
4634 return 0;