hey, i'm out of betas, so let it be 0.3.1beta1 now
[k8sterm.git] / src / sterm.c
blob618445ae83b038e1917424b24edb37dffd823a5d
1 /* See LICENSE for licence details. */
2 #define VERSION "0.3.1.beta1"
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 // uncomment the following to use XCreateGlyphCursor() instead of XCreatePixmapCursor()
41 //#define BLANKPTR_USE_GLYPH_CURSOR
44 //#define DUMP_KEYSYMS
46 //#define KEYPAD_DUMP
48 //#define DUMP_PROG_OUTPUT
49 //#define DUMP_PROG_INPUT
51 //#define DUMP_IO_READ
52 //#define DUMP_IO_WRITE
54 #if defined(DUMP_IO_READ) || defined(DUMP_IO_WRITE)
55 # define DUMP_IO
56 #endif
58 #ifdef KEYPAD_DUMP
59 # define DUMP_KEYPAD_SWITCH(strflag,strstate) do { fprintf(stderr, "KEYPAD %s (%s)\n", (strstate), (strflag)); } while (0)
60 #else
61 # define DUMP_KEYPAD_SWITCH(strflag,strstate) ((void)(sizeof(strflag)+sizeof(strstate)))
62 #endif
65 ////////////////////////////////////////////////////////////////////////////////
66 #define USAGE \
67 "sterm " VERSION " (c) 2010-2012 st engineers and Ketmar // Vampire Avalon\n" \
68 "usage: sterm [-t title] [-c class] [-w windowid] [-T termname] [-C config_file] [-l langiconv] [-S] [-v] [-e command...]\n"
71 ////////////////////////////////////////////////////////////////////////////////
72 #define FONT "-*-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"
73 #define FONTBOLD "-*-fixed-bold-r-normal-*-18-*-*-*-*-*-*-*"
74 #define FONTTAB "-*-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*"
77 /* Default shell to use if SHELL is not set in the env */
78 #define SHELL "/bin/sh"
81 /* Terminal colors (16 first used in escape sequence) */
82 static const char *defcolornames[] = {
83 #if 1
84 /* 8 normal colors */
85 "black",
86 "red3",
87 "green3",
88 "yellow3",
89 "blue2",
90 "magenta3",
91 "cyan3",
92 "gray90",
93 /* 8 bright colors */
94 "gray50",
95 "red",
96 "green",
97 "yellow",
98 "#5c5cff",
99 "magenta",
100 "cyan",
101 "white",
102 #else
103 /* 8 normal colors */
104 "#000000",
105 "#b21818",
106 "#18b218",
107 "#b26818",
108 "#1818b2",
109 "#b218b2",
110 "#18b2b2",
111 "#b2b2b2",
112 /* 8 bright colors */
113 "#686868",
114 "#ff5454",
115 "#54ff54",
116 "#ffff54",
117 "#5454ff",
118 "#ff54ff",
119 "#54ffff",
120 "#ffffff",
121 #endif
125 /* more colors can be added after 255 to use with DefaultXX */
126 static const char *defextcolornames[] = {
127 "#cccccc", /* 256 */
128 "#333333", /* 257 */
129 /* root terminal fg and bg */
130 "#809a70", /* 258 */
131 "#002000", /* 259 */
132 /* bold and underline */
133 "#00afaf", /* 260 */
134 "#00af00", /* 261 */
138 /* Default colors (colorname index) foreground, background, cursor, unfocused cursor */
139 #define DEFAULT_FG (7)
140 #define DEFAULT_BG (0)
141 #define DEFAULT_CS (256)
142 #define DEFAULT_UCS (257)
144 #define TNAME "xterm"
146 /* double-click timeout (in milliseconds) between clicks for selection */
147 #define DOUBLECLICK_TIMEOUT (300)
148 #define TRIPLECLICK_TIMEOUT (2*DOUBLECLICK_TIMEOUT)
149 //#define SELECT_TIMEOUT 20 /* 20 ms */
150 #define DRAW_TIMEOUT 18 /* 18 ms */
152 #define TAB (8)
155 ////////////////////////////////////////////////////////////////////////////////
156 #define MAX_COLOR (511)
158 /* XEMBED messages */
159 #define XEMBED_FOCUS_IN (4)
160 #define XEMBED_FOCUS_OUT (5)
163 /* Arbitrary sizes */
164 #define ESC_TITLE_SIZ (256)
165 #define ESC_BUF_SIZ (256)
166 #define ESC_ARG_SIZ (16)
167 #define DRAW_BUF_SIZ (2048)
168 #define UTF_SIZ (4)
169 #define OBUFSIZ (256)
170 #define WBUFSIZ (256)
173 /* masks for key translation */
174 #define XK_NO_MOD (UINT_MAX)
175 #define XK_ANY_MOD (0)
178 /* misc utility macros */
179 //#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000+(t1.tv_usec-t2.tv_usec)/1000)
180 #define SERRNO strerror(errno)
181 #define MIN(a, b) ((a) < (b) ? (a) : (b))
182 #define MAX(a, b) ((a) < (b) ? (b) : (a))
183 #define LEN(a) (sizeof(a)/sizeof(a[0]))
184 #define DEFAULT(a, b) (a) = ((a) ? (a) : (b))
185 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
186 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
187 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
188 #define IS_SET(flag) (term->mode&(flag))
189 #define X2COL(x) ((x)/xw.cw)
190 #define Y2ROW(y) ((y-(opt_tabposition==1 ? xw.tabheight : 0))/xw.ch-(term!=NULL ? term->topline : 0))
191 #define IS_GFX(mode) (((mode)&(ATTR_GFX|ATTR_G1)) == ATTR_GFX || ((mode)&(ATTR_GFX1|ATTR_G1)) == (ATTR_GFX1|ATTR_G1))
194 ////////////////////////////////////////////////////////////////////////////////
195 enum {
196 CMDMODE_NONE,
197 CMDMODE_INPUT,
198 CMDMODE_MESSAGE
201 enum glyph_attribute {
202 ATTR_NULL = 0x00,
203 ATTR_REVERSE = 0x01,
204 ATTR_UNDERLINE = 0x02,
205 ATTR_BOLD = 0x04,
206 ATTR_GFX = 0x08,
207 ATTR_DEFFG = 0x10,
208 ATTR_DEFBG = 0x20,
209 ATTR_G1 = 0x40,
210 ATTR_GFX1 = 0x80,
213 enum cursor_movement {
214 CURSOR_UP,
215 CURSOR_DOWN,
216 CURSOR_LEFT,
217 CURSOR_RIGHT,
218 CURSOR_SAVE,
219 CURSOR_LOAD
222 enum cursor_state {
223 CURSOR_DEFAULT = 0,
224 CURSOR_HIDE = 1,
225 CURSOR_WRAPNEXT = 2
228 enum glyph_state {
229 GLYPH_SET = 0x01, /* for selection only */
230 GLYPH_DIRTY = 0x02,
234 enum term_mode {
235 MODE_WRAP = 0x01,
236 MODE_INSERT = 0x02,
237 MODE_APPKEYPAD = 0x04,
238 MODE_ALTSCREEN = 0x08,
239 MODE_CRLF = 0x10,
240 MODE_MOUSEBTN = 0x20,
241 MODE_MOUSEMOTION = 0x40,
242 MODE_MOUSE = 0x20|0x40,
243 MODE_REVERSE = 0x80,
244 MODE_BRACPASTE = 0x100,
245 MODE_FOCUSEVT = 0x200,
246 MODE_DISPCTRL = 0x400, //TODO: not implemented yet
249 enum escape_state {
250 ESC_START = 0x01,
251 ESC_CSI = 0x02,
252 ESC_OSC = 0x04,
253 ESC_TITLE = 0x08,
254 ESC_ALTCHARSET = 0x10,
255 ESC_HASH = 0x20,
256 ESC_PERCENT = 0x40,
257 ESC_ALTG1 = 0x80,
260 enum window_state {
261 WIN_VISIBLE = 0x01,
262 WIN_REDRAW = 0x02,
263 WIN_FOCUSED = 0x04,
266 /* bit macro */
267 #undef B0
268 enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 };
271 ////////////////////////////////////////////////////////////////////////////////
272 typedef unsigned char uchar;
273 typedef unsigned int uint;
274 typedef unsigned long ulong;
275 typedef unsigned short ushort;
278 typedef struct __attribute__((packed)) {
279 char c[UTF_SIZ]; /* character code */
280 uchar mode; /* attribute flags */
281 ushort fg; /* foreground */
282 ushort bg; /* background */
283 uchar state; /* state flags */
284 } Glyph;
287 typedef Glyph *Line;
289 typedef struct {
290 Glyph attr; /* current char attributes */
291 int x;
292 int y;
293 char state;
294 } TCursor;
297 /* CSI Escape sequence structs */
298 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
299 typedef struct {
300 char buf[ESC_BUF_SIZ]; /* raw string */
301 int len; /* raw string length */
302 char priv;
303 int arg[ESC_ARG_SIZ];
304 int narg; /* nb of args */
305 char mode;
306 } CSIEscape;
309 /* Purely graphic info */
310 typedef struct {
311 Display *dpy;
312 Colormap cmap;
313 Window win;
314 Cursor cursor;
315 Cursor blankPtr;
316 Atom xembed;
317 XIM xim;
318 XIC xic;
319 int scr;
320 int w; /* window width */
321 int h; /* window height */
322 int bufw; /* pixmap width */
323 int bufh; /* pixmap height */
324 int ch; /* char height */
325 int cw; /* char width */
326 char state; /* focus, redraw, visible */
328 int tch; /* tab text char height */
329 Pixmap pictab;
330 int tabheight;
331 //struct timeval lastdraw;
332 } XWindow;
335 /* TODO: use better name for vars... */
336 typedef struct {
337 int mode;
338 int bx, by;
339 int ex, ey;
340 struct { int x, y; } b, e;
341 char *clip;
342 Atom xtarget;
343 int tclick1;
344 int tclick2;
345 } Selection;
348 /* Drawing Context */
349 typedef struct {
350 ulong *ncol; // normal colors
351 ulong *bcol; // b/w colors
352 ulong *gcol; // green colors
353 ulong *clrs[3];
354 GC gc;
355 struct {
356 int ascent;
357 int descent;
358 short lbearing;
359 short rbearing;
360 XFontSet set;
361 Font fid;
362 } font[3];
363 } DC;
366 #define CMDLINE_SIZE (256)
368 /* Internal representation of the screen */
369 typedef struct {
370 int cmdfd;
371 int dead;
372 int exitcode;
373 int needConv; /* 0: utf-8 locale */
374 int audiblebell;
375 int blackandwhite;
377 int row; /* nb row */
378 int col; /* nb col */
379 int topline; /* top line for drawing (0: no history; 1: show one history line; etc) */
380 int linecount; /* full, with history */
381 int maxhistory;/* max history lines; 0: none; <0: infinite */
382 Line *line; /* screen */
383 Line *alt; /* alternate screen */
384 char *dirty; /* dirtyness of lines */
385 TCursor c; /* cursor */
386 int top; /* top scroll limit */
387 int bot; /* bottom scroll limit */
388 int mode; /* terminal mode flags */
389 int esc; /* escape state flags */
391 TCursor csaved; /* saved cursor info */
392 // old cursor position
393 int oldcx;
394 int oldcy;
396 char title[ESC_TITLE_SIZ+1];
397 int titlelen;
399 int mouseob;
400 int mouseox;
401 int mouseoy;
403 char obuf[OBUFSIZ];
404 #ifdef DUMP_PROG_OUTPUT
405 int xobuflen;
406 #endif
407 int obuflen;
409 char ubuf[UTF_SIZ];
410 int ubufpos;
412 char drawbuf[DRAW_BUF_SIZ];
414 char wrbuf[WBUFSIZ];
415 int wrbufsize;
416 int wrbufused;
417 int wrbufpos;
419 CSIEscape escseq;
420 Selection sel;
421 pid_t pid;
422 int lastDrawTime;
424 char *execcmd;
426 Pixmap picbuf;
427 int picbufw;
428 int picbufh;
430 ushort deffg;
431 ushort defbg;
433 int wantRedraw;
435 int lastActiveTime;
437 int cmdMode;
438 char cmdline[UTF_SIZ*CMDLINE_SIZE];
439 int cmdofs;
440 char cmdc[UTF_SIZ+1];
441 int cmdcl;
442 } Term;
445 ////////////////////////////////////////////////////////////////////////////////
446 /* Globals */
447 static ushort *unimap = NULL; // 0 or 65535 items; 127: special; high bit set: use alt(gfx) mode; 0: empty
449 static char **opt_cmd = NULL;
450 static char *opt_title = NULL;
451 static char *opt_embed = NULL;
452 static char *opt_class = NULL;
453 static char *opt_term = NULL;
454 static char *opt_fontnorm = NULL;
455 static char *opt_fontbold = NULL;
456 static char *opt_fonttab = NULL;
457 static char *opt_shell = NULL;
458 static char *opt_colornames[512];
459 static int opt_term_locked = 0;
460 static int opt_doubleclick_timeout = DOUBLECLICK_TIMEOUT;
461 static int opt_tripleclick_timeout = TRIPLECLICK_TIMEOUT;
462 static int opt_tabsize = TAB;
463 static int defaultFG = DEFAULT_FG;
464 static int defaultBG = DEFAULT_BG;
465 static int defaultCursorFG = 0;
466 static int defaultCursorBG = DEFAULT_CS;
467 static int defaultCursorInactiveFG = 0;
468 static int defaultCursorInactiveBG = DEFAULT_UCS;
469 static int defaultBoldFG = -1;
470 static int defaultUnderlineFG = -1;
471 static int normalTabFG = 258;
472 static int normalTabBG = 257;
473 static int activeTabFG = 258;
474 static int activeTabBG = 0;
475 static int opt_maxhistory = 512;
476 static int opt_ptrblank = 2000; // delay; 0: never
477 static int opt_tabcount = 6;
478 static int opt_tabposition = 0;
479 static int opt_drawtimeout = DRAW_TIMEOUT;
480 static int opt_disabletabs = 0;
481 static int opt_audiblebell = 1;
482 static int ptrBlanked = 0;
483 static int ptrLastMove = 0;
484 static int globalBW = 0;
486 static Term **term_array = NULL;
487 static int term_count = 0;
488 static int term_array_size = 0;
489 static Term *term; // current terminal
490 static int termidx; // current terminal index; DON'T RELAY ON IT!
491 static int updateTabBar;
492 static int lastDrawTime = 0;
493 static char *lastSelStr = NULL;
494 //static int lastSelLength = 0;
496 static int exitcode = 0;
498 static DC dc;
499 static XWindow xw;
501 static Atom XA_VT_SELECTION;
502 static Atom XA_CLIPBOARD;
503 static Atom XA_UTF8;
504 static Atom XA_TARGETS;
505 static Atom XA_NETWM_NAME;
508 ////////////////////////////////////////////////////////////////////////////////
509 typedef struct {
510 KeySym src;
511 KeySym dst;
512 } KeyTransDef;
515 static KeyTransDef *keytrans = NULL;
516 static int keytrans_size = 0;
517 static int keytrans_used = 0;
520 typedef struct {
521 KeySym key;
522 uint mask;
523 int kp;
524 char *str;
525 } KeyInfoDef;
528 static KeyInfoDef *keybinds = NULL;
529 static int keybinds_size = 0;
530 static int keybinds_used = 0;
532 static KeyInfoDef *keymap = NULL;
533 static int keymap_size = 0;
534 static int keymap_used = 0;
537 ////////////////////////////////////////////////////////////////////////////////
538 static void executeCommands (const char *str);
540 static void ttyresize (void);
541 static void tputc (const char *c); // `c` is utf-8
542 static void ttywrite (const char *s, size_t n);
543 static void tsetdirt (int top, int bot);
544 static void tfulldirt (void);
546 static void xseturgency (int add);
547 static void xfixsel (void);
548 static void xblankPointer (void);
549 static void xunblankPointer (void);
551 static void draw (int forced);
553 static void tcmdput (const char *s, int len);
556 static inline void ttywritestr (const char *s) { if (s != NULL && s[0]) ttywrite(s, strlen(s)); }
559 static inline ulong getColor (int idx) {
560 if (globalBW) return dc.clrs[globalBW][idx];
561 if (term != NULL) return dc.clrs[term->blackandwhite][idx];
562 return dc.clrs[0][idx];
564 if (globalBW) return dc.bcol[idx];
565 if (term != NULL) {
566 return (term->blackandwhite ? dc.bcol[idx] : dc.ncol[idx]);
568 return dc.ncol[idx];
573 ////////////////////////////////////////////////////////////////////////////////
575 static void trimstr (char *s) {
576 char *e;
578 while (*s && isspace(*s)) ++s;
579 for (e = s+strlen(s); e > s; --e) if (!isspace(e[-1])) break;
580 if (e <= s) *s = 0; else *e = 0;
584 // parse the argument list
585 // return error message or NULL
586 // format:
587 // '*': skip
588 // 's': string (char *)
589 // 'i': integer (int *)
590 // 'b': boolean (int *)
591 // '|': optional arguments follows
592 // '.': stop parsing, ignore rest
593 // 'R': stop parsing, set rest ptr (char *)
594 // string modifiers (also for 'R'):
595 // '!' -- don't allow empty strings
596 // '-' -- trim spaces
597 // int modifiers:
598 // {lo,hi}
599 // {,hi}
600 // {lo}
601 // WARNING! `line` will be modified!
602 // WARNING! string pointers will point INSIDE `line`, so don't discard it!
603 // UGLY! REWRITE!
604 const char *iniParseArguments (char *line, const char *fmt, ...) {
605 va_list ap;
606 int inOptional = 0;
608 if (line == NULL) return "alas";
609 trimstr(line);
610 va_start(ap, fmt);
611 while (*fmt) {
612 char spec = *fmt++, *args;
614 if (spec == '|') { inOptional = 1; continue; }
615 if (spec == '.') { va_end(ap); return NULL; }
617 while (*line && isspace(*line)) ++line;
618 if (*line == '#') *line = 0;
620 if (spec == 'R') {
621 char **p = va_arg(ap, char **);
622 int noempty = 0;
624 while (*fmt) {
625 if (*fmt == '!') { ++fmt; noempty = 1; }
626 else if (*fmt == '-') { ++fmt; trimstr(line); }
627 else break;
629 va_end(ap);
630 if (noempty && !line[0]) return "invalid empty arg";
631 if (p != NULL) *p = line;
632 return NULL;
635 if (!line[0]) {
636 // end of line, stop right here
637 va_end(ap);
638 if (!inOptional) return "out of args";
639 return NULL;
642 args = line;
644 char *dest = args, qch = '#';
645 int n;
647 if (line[0] == '"' || line[0] == '\'') qch = *line++;
649 while (*line && *line != qch) {
650 if (qch == '#' && isspace(*line)) break;
652 if (*line == '\\') {
653 if (!line[1]) { va_end(ap); return "invalid escape"; }
654 switch (*(++line)) {
655 case 'n': *dest++ = '\n'; ++line; break;
656 case 'r': *dest++ = '\r'; ++line; break;
657 case 't': *dest++ = '\t'; ++line; break;
658 case 'a': *dest++ = '\a'; ++line; break;
659 case 'e': *dest++ = '\x1b'; ++line; break; // esc
660 case 's': *dest++ = ' '; ++line; break;
661 case 'x': // hex
662 ++line;
663 if (!isxdigit(*line)) { va_end(ap); return "invalid hex escape"; }
664 n = toupper(*line)-'0'; if (n > 9) n -= 7;
665 ++line;
666 if (isxdigit(*line)) {
667 int b = toupper(*line)-'0'; if (b > 9) b -= 7;
669 n = (n*16)+b;
670 ++line;
672 *dest++ = n;
673 break;
674 case '0': // octal
675 n = 0;
676 for (int f = 0; f < 4; ++f) {
677 if (*line < '0' || *line > '7') break;
678 n = (n*8)+(line[0]-'0');
679 if (n > 255) { va_end(ap); return "invalid oct escape"; }
680 ++line;
682 if (n == 0) { va_end(ap); return "invalid oct escape"; }
683 *dest++ = n;
684 break;
685 case '1'...'9': // decimal
686 n = 0;
687 for (int f = 0; f < 3; ++f) {
688 if (*line < '0' || *line > '9') break;
689 n = (n*8)+(line[0]-'0');
690 if (n > 255) { va_end(ap); return "invalid dec escape"; }
691 ++line;
693 if (n == 0) { va_end(ap); return "invalid oct escape"; }
694 *dest++ = n;
695 break;
696 default:
697 *dest++ = *line++;
698 break;
700 } else {
701 *dest++ = *line++;
704 if (qch != '#') {
705 if (*line != qch) return "unfinished string";
706 if (*line) ++line;
707 } else if (*line && *line != '#') ++line;
708 *dest = 0;
710 // now process and convert argument
711 switch (spec) {
712 case '*': /* skip */
713 break;
714 case 's': { /* string */
715 int noempty = 0, trim = 0;
716 char **p;
718 for (;;) {
719 if (*fmt == '!') { noempty = 1; ++fmt; }
720 else if (*fmt == '-') { trim = 1; ++fmt; }
721 else break;
724 if (trim) trimstr(args);
726 if (noempty && !args[0]) { va_end(ap); return "invalid empty string"; }
727 p = va_arg(ap, char **);
728 if (p != NULL) *p = args;
729 } break;
730 case 'i': /* int */
731 if (!args[0]) {
732 va_end(ap);
733 return "invalid integer";
734 } else {
735 int *p = va_arg(ap, int *);
736 long int n;
737 char *eptr;
739 trimstr(args);
740 n = strtol(args, &eptr, 0);
741 if (*eptr) { va_end(ap); return "invalid integer"; }
743 if (*fmt == '{') {
744 // check min/max
745 int minmax[2], haveminmax[2];
747 haveminmax[0] = haveminmax[1] = 0;
748 minmax[0] = minmax[1] = 0;
749 ++fmt;
750 for (int f = 0; f < 2; ++f) {
751 if (isdigit(*fmt) || *fmt == '-' || *fmt == '+') {
752 int neg = 0;
753 haveminmax[f] = 1;
754 if (*fmt == '-') neg = 1;
755 if (!isdigit(*fmt)) {
756 ++fmt;
757 if (!isdigit(*fmt)) { va_end(ap); return "invalid integer bounds"; }
759 while (isdigit(*fmt)) {
760 minmax[f] = minmax[f]*10+(fmt[0]-'0');
761 ++fmt;
763 if (neg) minmax[f] = -minmax[f];
764 //fprintf(stderr, "got: %d\n", minmax[f]);
766 if (*fmt == ',') {
767 if (f == 1) { va_end(ap); return "invalid integer bounds: extra comma"; }
768 // do nothing, we are happy
769 ++fmt;
770 } else if (*fmt == '}') {
771 // ok, done
772 break;
773 } else { va_end(ap); return "invalid integer bounds"; }
775 if (*fmt != '}') { va_end(ap); return "invalid integer bounds"; }
776 ++fmt;
778 //fprintf(stderr, "b: (%d,%d) (%d,%d)\n", haveminmax[0], minmax[0], haveminmax[1], minmax[1]);
779 if ((haveminmax[0] && n < minmax[0]) || (haveminmax[1] && n > minmax[1])) { va_end(ap); return "integer out of bounds"; }
782 if (p) *p = n;
784 break;
785 case 'b': { /* bool */
786 int *p = va_arg(ap, int *);
788 trimstr(args);
789 if (!args[0]) { va_end(ap); return "invalid boolean"; }
790 if (strcasecmp(args, "true") == 0 || strcasecmp(args, "on") == 0 ||
791 strcasecmp(args, "tan") == 0 || strcasecmp(args, "1") == 0) {
792 if (p) *p = 1;
793 } else if (strcasecmp(args, "false") == 0 || strcasecmp(args, "off") == 0 ||
794 strcasecmp(args, "ona") == 0 || strcasecmp(args, "0") == 0) {
796 if (p) *p = 0;
797 } else {
798 va_end(ap);
799 return "invalid boolean";
801 } break;
802 default:
803 va_end(ap);
804 return "invalid format specifier";
807 va_end(ap);
808 while (*line && isspace(*line)) ++line;
809 if (!line[0] || line[0] == '#') return NULL;
810 return "extra args";
814 ////////////////////////////////////////////////////////////////////////////////
815 // UTF-8
816 static int utf8decode (const char *s, ulong *u) {
817 uchar c;
818 int n, rtn;
820 rtn = 1;
821 c = *s;
822 if (~c & B7) { /* 0xxxxxxx */
823 *u = c;
824 return rtn;
825 } else if ((c & (B7|B6|B5)) == (B7|B6)) { /* 110xxxxx */
826 *u = c&(B4|B3|B2|B1|B0);
827 n = 1;
828 } else if ((c & (B7|B6|B5|B4)) == (B7|B6|B5)) { /* 1110xxxx */
829 *u = c&(B3|B2|B1|B0);
830 n = 2;
831 } else if ((c & (B7|B6|B5|B4|B3)) == (B7|B6|B5|B4)) { /* 11110xxx */
832 *u = c & (B2|B1|B0);
833 n = 3;
834 } else {
835 goto invalid;
837 ++s;
838 for (int f = n; f > 0; --f, ++rtn, ++s) {
839 c = *s;
840 if ((c & (B7|B6)) != B7) goto invalid; /* 10xxxxxx */
841 *u <<= 6;
842 *u |= c & (B5|B4|B3|B2|B1|B0);
844 if ((n == 1 && *u < 0x80) ||
845 (n == 2 && *u < 0x800) ||
846 (n == 3 && *u < 0x10000) ||
847 (*u >= 0xD800 && *u <= 0xDFFF)) {
848 goto invalid;
850 return rtn;
851 invalid:
852 *u = 0xFFFD;
853 return rtn;
857 static int utf8encode (ulong u, char *s) {
858 uchar *sp;
859 ulong uc;
860 int n;
862 sp = (uchar *)s;
863 uc = u&0x1fffff;
864 if (uc < 0x80) {
865 *sp = uc; /* 0xxxxxxx */
866 return 1;
867 } else if (uc < 0x800) {
868 *sp = (uc >> 6) | (B7|B6); /* 110xxxxx */
869 n = 1;
870 } else if (uc < 0x10000) {
871 *sp = (uc >> 12) | (B7|B6|B5); /* 1110xxxx */
872 n = 2;
873 } else if (uc <= 0x10FFFF) {
874 *sp = (uc >> 18) | (B7|B6|B5|B4); /* 11110xxx */
875 n = 3;
876 } else {
877 goto invalid;
879 ++sp;
880 for (int f = n; f > 0; --f, ++sp) *sp = ((uc >> 6*(f-1)) & (B5|B4|B3|B2|B1|B0)) | B7; /* 10xxxxxx */
881 return n+1;
882 invalid:
883 /* U+FFFD */
884 *s++ = '\xEF';
885 *s++ = '\xBF';
886 *s = '\xBD';
887 return 3;
891 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
892 UTF-8 otherwise return 0 */
893 static int isfullutf8 (const char *s, int b) {
894 uchar *c1, *c2, *c3;
896 c1 = (uchar *) s;
897 c2 = (uchar *) ++s;
898 c3 = (uchar *) ++s;
899 if (b < 1) return 0;
900 if ((*c1&(B7|B6|B5)) == (B7|B6) && b == 1) return 0;
901 if ((*c1&(B7|B6|B5|B4)) == (B7|B6|B5) && (b == 1 || (b == 2 && (*c2&(B7|B6)) == B7))) return 0;
902 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;
903 return 1;
907 static int utf8size (const char *s) {
908 uchar c = *s;
910 if (~c&B7) return 1;
911 if ((c&(B7|B6|B5)) == (B7|B6)) return 2;
912 if ((c&(B7|B6|B5|B4)) == (B7|B6|B5)) return 3;
913 return 4;
917 static int utf8strlen (const char *s) {
918 int len = 0;
920 while (*s) {
921 if (((unsigned char)(s[0])&0xc0) == 0xc0 || ((unsigned char)(s[0])&0x80) == 0) ++len;
922 ++s;
924 return len;
928 static void utf8choplast (char *s) {
929 int lastpos = 0;
931 for (char *t = s; *t; ++t) {
932 if (((unsigned char)(t[0])&0xc0) == 0xc0 || ((unsigned char)(t[0])&0x80) == 0) lastpos = (int)(t-s);
934 s[lastpos] = 0;
938 ////////////////////////////////////////////////////////////////////////////////
939 // utilities
940 static char *SPrintfVA (const char *fmt, va_list vaorig) {
941 char *buf = NULL;
942 int olen, len = 128;
944 buf = malloc(len);
945 if (buf == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
946 for (;;) {
947 char *nb;
948 va_list va;
950 va_copy(va, vaorig);
951 olen = vsnprintf(buf, len, fmt, va);
952 va_end(va);
953 if (olen >= 0 && olen < len) return buf;
954 if (olen < 0) olen = len*2-1;
955 nb = realloc(buf, olen+1);
956 if (nb == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
957 buf = nb;
958 len = olen+1;
963 static __attribute__((format(printf,1,2))) char *SPrintf (const char *fmt, ...) {
964 char *buf = NULL;
965 va_list va;
967 va_start(va, fmt);
968 buf = SPrintfVA(fmt, va);
969 va_end(va);
970 return buf;
974 static __attribute__((noreturn)) __attribute__((format(printf,1,2))) void die (const char *errstr, ...) {
975 va_list ap;
977 fprintf(stderr, "FATAL: ");
978 va_start(ap, errstr);
979 vfprintf(stderr, errstr, ap);
980 va_end(ap);
981 fprintf(stderr, "\n");
982 exit(EXIT_FAILURE);
986 ////////////////////////////////////////////////////////////////////////////////
987 // getticks
988 static struct timespec mclk_sttime; // starting time of monotonic clock
991 static void mclock_init (void) {
992 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &mclk_sttime);
996 static uint mclock_ticks (void) {
997 struct timespec tp;
999 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &tp);
1000 tp.tv_sec -= mclk_sttime.tv_sec;
1001 return tp.tv_sec*1000+(tp.tv_nsec/1000000);
1005 ////////////////////////////////////////////////////////////////////////////////
1006 // locale conversions
1007 static iconv_t icFromLoc;
1008 static iconv_t icToLoc;
1009 static int needConversion = 0;
1010 static const char *cliLocale = NULL;
1013 static void initLCConversion (void) {
1014 const char *lct = setlocale(LC_CTYPE, NULL);
1015 char *cstr;
1017 needConversion = 0;
1018 if (cliLocale == NULL) {
1019 if (strrchr(lct, '.') != NULL) lct = strrchr(lct, '.')+1;
1020 } else {
1021 lct = cliLocale;
1023 if (strcasecmp(lct, "utf8") == 0 || strcasecmp(lct, "utf-8") == 0) return;
1024 //fprintf(stderr, "locale: [%s]\n", lct);
1025 icFromLoc = iconv_open("UTF-8", lct);
1026 if (icFromLoc == (iconv_t)-1) die("can't initialize locale conversion");
1027 cstr = SPrintf("%s//TRANSLIT", lct);
1028 icToLoc = iconv_open(cstr, "UTF-8");
1029 free(cstr);
1030 if (icToLoc == (iconv_t)-1) die("can't initialize locale conversion");
1031 needConversion = 1;
1035 static int loc2utf (char *dest, const char *src, int len) {
1036 if (needConversion) {
1037 char *ibuf, *obuf;
1038 size_t il, ol, ool;
1040 ibuf = (char *)src;
1041 obuf = dest;
1042 il = len;
1043 ool = ol = il*4;
1044 il = iconv(icFromLoc, &ibuf, &il, &obuf, &ol);
1045 if (il == (size_t)-1) return 0;
1046 return ool-ol;
1047 } else {
1048 if (len > 0) memmove(dest, src, len);
1049 return len;
1054 static int utf2loc (char *dest, const char *src, int len) {
1055 if (needConversion) {
1056 char *ibuf, *obuf;
1057 size_t il, ol, ool;
1059 ibuf = (char *)src;
1060 obuf = dest;
1061 il = len;
1062 ool = ol = il*4;
1063 il = iconv(icToLoc, &ibuf, &il, &obuf, &ol);
1064 if (il == (size_t)-1) return 0;
1065 return ool-ol;
1066 } else {
1067 if (len > 0) memmove(dest, src, len);
1068 return len;
1073 ////////////////////////////////////////////////////////////////////////////////
1074 static void fixWindowTitle (const Term *t) {
1075 const char *title = (t != NULL) ? t->title : NULL;
1077 if (title == NULL || !title[0]) {
1078 title = opt_title;
1079 if (title == NULL) title = "";
1081 XStoreName(xw.dpy, xw.win, title);
1082 XChangeProperty(xw.dpy, xw.win, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, (const unsigned char *)title, strlen(title));
1086 // find latest active terminal (but not current %-)
1087 static int findTermToSwitch (void) {
1088 int maxlat = -1, idx = -1;
1090 for (int f = 0; f < term_count; ++f) {
1091 if (term != term_array[f] && term_array[f]->lastActiveTime > maxlat) {
1092 maxlat = term_array[f]->lastActiveTime;
1093 idx = f;
1096 if (idx < 0) {
1097 if (termidx == 0) idx = 0; else idx = termidx+1;
1098 if (idx > term_count) idx = term_count-1;
1100 return idx;
1104 static void switchToTerm (int idx, int redraw) {
1105 if (idx >= 0 && idx < term_count && term_array[idx] != NULL && term_array[idx] != term) {
1106 if (term != NULL) term->lastActiveTime = mclock_ticks();
1107 termidx = idx;
1108 term = term_array[termidx];
1109 xseturgency(0);
1110 tfulldirt();
1111 fixWindowTitle(term);
1112 updateTabBar = 1;
1113 if (redraw) draw(1);
1114 //FIXME: optimize memory allocations
1115 if (term->sel.clip != NULL && term->sel.bx >= 0) {
1116 if (lastSelStr != NULL) free(lastSelStr);
1117 lastSelStr = strdup(term->sel.clip);
1118 //fprintf(stderr, "newsel: [%s]\n", lastSelStr);
1120 xfixsel();
1121 //fprintf(stderr, "term #%d\n", termidx);
1122 //fprintf(stderr, "needConv: %d\n", term->needConv);
1127 static Term *termalloc (void) {
1128 Term *t;
1130 if (term_count >= term_array_size) {
1131 int newsz = (term_count==0) ? 1 : term_array_size+64;
1132 Term **n = realloc(term_array, sizeof(Term *)*newsz);
1134 if (n == NULL && term_count == 0) die("out of memory!");
1135 term_array = n;
1136 term_array_size = newsz;
1138 if ((t = calloc(1, sizeof(Term))) == NULL) return NULL;
1139 t->wrbufsize = WBUFSIZ;
1140 t->deffg = defaultFG;
1141 t->defbg = defaultBG;
1142 t->dead = 1;
1143 t->needConv = (needConversion ? 1 : 0);
1144 t->audiblebell = opt_audiblebell;
1145 term_array[term_count++] = t;
1146 return t;
1150 // newer delete last terminal!
1151 static void termfree (int idx) {
1152 if (idx >= 0 && idx < term_count && term_array[idx] != NULL) {
1153 Term *t = term_array[idx];
1155 if (t->pid != 0) {
1156 kill(t->pid, SIGKILL);
1157 return;
1159 if (t->cmdfd >= 0) {
1160 close(t->cmdfd);
1161 t->cmdfd = -1;
1163 exitcode = t->exitcode;
1164 if (idx == termidx) {
1165 if (term_count > 1) {
1166 t->dead = 1;
1167 //switchToTerm((idx > 0) ? idx-1 : 1, 0);
1168 switchToTerm(findTermToSwitch(), 0);
1169 return;
1171 term = NULL;
1173 for (int y = 0; y < t->row; ++y) free(t->alt[y]);
1174 for (int y = 0; y < t->linecount; ++y) {
1175 //fprintf(stderr, "y=%d\n", y);
1176 free(t->line[y]);
1178 free(t->dirty);
1179 free(t->alt);
1180 free(t->line);
1181 if (t->execcmd != NULL) free(t->execcmd);
1182 // condense array
1183 if (termidx > idx) {
1184 // not current, and current at the right
1185 --termidx;
1187 for (int f = idx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
1188 --term_count;
1189 XFreePixmap(xw.dpy, t->picbuf);
1190 free(t);
1195 static void termcleanup (void) {
1196 int f = 0, needredraw = 0;
1198 while (f < term_count) {
1199 if (term_array[f]->dead) {
1200 termfree(f);
1201 needredraw = 1;
1202 } else {
1203 ++f;
1206 if (needredraw) {
1207 updateTabBar = 1;
1208 draw(1);
1213 //FIXME: is it safe to assume that signal interrupted main program?
1214 static void sigchld (int a) {
1215 //if (waitpid(term->pid, &stat, 0) < 0) die("waiting for pid %hd failed: %s", term->pid, SERRNO);
1216 for (;;) {
1217 int stat = 0;
1218 pid_t res = waitpid(-1, &stat, WNOHANG);
1220 if (res == (pid_t)-1 || res == 0) break;
1222 for (int f = 0; f < term_count; ++f) {
1223 if (term_array[f]->pid == res) {
1224 // this terminal should die
1225 //if (term_count == 1) exit(0);
1226 //close(term_array[f]->cmdfd);
1227 //term_array[f]->cmdfd = -1;
1228 term_array[f]->dead = 1;
1229 term_array[f]->pid = 0;
1230 term_array[f]->exitcode = (WIFEXITED(stat)) ? WEXITSTATUS(stat) : 127;
1231 //fprintf(stderr, "exitcode=%d\n", term_array[f]->exitcode);
1232 updateTabBar = 1;
1233 break;
1237 signal(SIGCHLD, sigchld);
1241 ////////////////////////////////////////////////////////////////////////////////
1242 static void keytrans_reset (void) {
1243 if (keytrans) free(keytrans);
1244 keytrans = NULL;
1245 keytrans_size = 0;
1246 keytrans_used = 0;
1250 static void keytrans_add (const char *src, const char *dst) {
1251 KeySym kssrc = XStringToKeysym(src);
1252 KeySym ksdst = XStringToKeysym(dst);
1254 if (kssrc == NoSymbol) die("invalid keysym: '%s'", src);
1255 if (ksdst == NoSymbol) die("invalid keysym: '%s'", dst);
1256 if (kssrc == ksdst) return; // idiot
1258 for (int f = 0; f < keytrans_used; ++f) {
1259 if (keytrans[f].src == kssrc) {
1260 // replace
1261 keytrans[f].dst = ksdst;
1262 return;
1266 if (keytrans_used >= keytrans_size) {
1267 int newsize = keytrans_size+64;
1268 KeyTransDef *n = realloc(keytrans, sizeof(KeyTransDef)*newsize);
1270 if (n == NULL) die("out of memory");
1271 keytrans_size = newsize;
1272 keytrans = n;
1274 keytrans[keytrans_used].src = kssrc;
1275 keytrans[keytrans_used].dst = ksdst;
1276 ++keytrans_used;
1280 ////////////////////////////////////////////////////////////////////////////////
1281 static void parsekeyname (const char *str, KeySym *ks, uint *mask, int *kp) {
1282 char *s = alloca(strlen(str)+1);
1284 if (s == NULL) die("out of memory");
1285 strcpy(s, str);
1286 *kp = 0;
1287 *ks = NoSymbol;
1288 *mask = XK_NO_MOD;
1290 while (*s) {
1291 char *e, oc;
1292 uint mm = 0;
1293 int mod = 1;
1295 while (*s && isspace(*s)) ++s;
1296 for (e = s; *e && !isspace(*e) && *e != '+'; ++e) ;
1297 oc = *e; *e = 0;
1299 if (strcasecmp(s, "alt") == 0) mm = Mod1Mask;
1300 else if (strcasecmp(s, "win") == 0) mm = Mod4Mask;
1301 else if (strcasecmp(s, "ctrl") == 0) mm = ControlMask;
1302 else if (strcasecmp(s, "shift") == 0) mm = ShiftMask;
1303 else if (strcasecmp(s, "any") == 0) mm = XK_NO_MOD; //!
1304 else if (strcasecmp(s, "kpad") == 0) *kp = 1;
1305 else {
1306 mod = 0;
1307 if ((*ks = XStringToKeysym(s)) == NoSymbol) break;
1308 //fprintf(stderr, "[%s]\n", s);
1311 *e = oc;
1312 s = e;
1313 while (*s && isspace(*s)) ++s;
1314 if (mod) {
1315 if (*s != '+') { *ks = NoSymbol; break; }
1316 ++s;
1317 if (mm != 0) {
1318 if (mm == XK_NO_MOD) *mask = XK_ANY_MOD;
1319 else if (*mask == XK_NO_MOD) *mask = mm;
1320 else if (*mask != XK_ANY_MOD) *mask |= mm;
1322 } else {
1323 if (*s) { *ks = NoSymbol; break; }
1326 if (*ks == NoSymbol) die("invalid key name: '%s'", str);
1327 //fprintf(stderr, "mask=0x%08x, kp=%d\n", *mask, *kp);
1331 ////////////////////////////////////////////////////////////////////////////////
1332 static void keybinds_reset (void) {
1333 if (keybinds) free(keybinds);
1334 keybinds = NULL;
1335 keybinds_size = 0;
1336 keybinds_used = 0;
1340 static void keybind_add (const char *key, const char *act) {
1341 KeySym ks;
1342 uint mask;
1343 int kp;
1345 parsekeyname(key, &ks, &mask, &kp);
1347 for (int f = 0; f < keybinds_used; ++f) {
1348 if (keybinds[f].key == ks && keybinds[f].mask == mask) {
1349 // replace or remove
1350 free(keybinds[f].str);
1351 if (act == NULL || !act[0]) {
1352 // remove
1353 for (int c = f+1; c < keybinds_used; ++c) keybinds[c-1] = keybinds[c];
1354 } else {
1355 // replace
1356 if ((keybinds[f].str = strdup(act)) == NULL) die("out of memory");
1358 return;
1362 if (keybinds_used >= keybinds_size) {
1363 int newsize = keybinds_size+64;
1364 KeyInfoDef *n = realloc(keybinds, sizeof(KeyInfoDef)*newsize);
1366 if (n == NULL) die("out of memory");
1367 keybinds_size = newsize;
1368 keybinds = n;
1370 keybinds[keybinds_used].key = ks;
1371 keybinds[keybinds_used].mask = mask;
1372 keybinds[keybinds_used].kp = 0;
1373 if ((keybinds[keybinds_used].str = strdup(act)) == NULL) die("out of memory");
1374 ++keybinds_used;
1378 ////////////////////////////////////////////////////////////////////////////////
1379 static void keymap_reset (void) {
1380 if (keymap) free(keymap);
1381 keymap = NULL;
1382 keymap_size = 0;
1383 keymap_used = 0;
1387 static void keymap_add (const char *key, const char *act) {
1388 KeySym ks;
1389 uint mask;
1390 int kp;
1392 parsekeyname(key, &ks, &mask, &kp);
1394 for (int f = 0; f < keymap_used; ++f) {
1395 if (keymap[f].key == ks && keymap[f].mask == mask && keymap[f].kp == kp) {
1396 // replace or remove
1397 free(keymap[f].str);
1398 if (act == NULL) {
1399 // remove
1400 for (int c = f+1; c < keymap_used; ++c) keymap[c-1] = keymap[c];
1401 } else {
1402 // replace
1403 if ((keymap[f].str = strdup(act)) == NULL) die("out of memory");
1405 return;
1409 if (keymap_used >= keymap_size) {
1410 int newsize = keymap_size+128;
1411 KeyInfoDef *n = realloc(keymap, sizeof(KeyInfoDef)*newsize);
1413 if (n == NULL) die("out of memory");
1414 keymap_size = newsize;
1415 keymap = n;
1417 keymap[keymap_used].key = ks;
1418 keymap[keymap_used].mask = mask;
1419 keymap[keymap_used].kp = kp;
1420 if ((keymap[keymap_used].str = strdup(act)) == NULL) die("out of memory");
1421 ++keymap_used;
1425 ////////////////////////////////////////////////////////////////////////////////
1426 // selection
1427 static void inline markDirty (int lineno, int flag) {
1428 if (term != NULL && lineno >= 0 && lineno < term->row) {
1429 term->dirty[lineno] |= flag;
1430 term->wantRedraw = 1;
1431 term->lastDrawTime = mclock_ticks(); //FIXME: avoid excess redraw?
1436 static void selinit (void) {
1437 term->sel.tclick1 = term->sel.tclick2 = mclock_ticks();
1438 term->sel.mode = 0;
1439 term->sel.bx = -1;
1440 term->sel.clip = NULL;
1441 term->sel.xtarget = XA_UTF8;
1442 //if (term->sel.xtarget == None) term->sel.xtarget = XA_STRING;
1446 static void selhide (void) {
1447 if (term->sel.bx != -1) {
1448 term->sel.mode = 0;
1449 term->sel.bx = -1;
1450 tfulldirt();
1455 static inline int selected (int x, int y) {
1456 if (term->sel.bx == -1) return 0;
1457 if (term->sel.ey == y && term->sel.by == y) {
1458 int bx = MIN(term->sel.bx, term->sel.ex);
1459 int ex = MAX(term->sel.bx, term->sel.ex);
1461 return BETWEEN(x, bx, ex);
1463 return
1464 ((term->sel.b.y < y && y < term->sel.e.y) || (y == term->sel.e.y && x <= term->sel.e.x)) ||
1465 (y == term->sel.b.y && x >= term->sel.b.x && (x <= term->sel.e.x || term->sel.b.y != term->sel.e.y));
1469 static void getbuttoninfo (XEvent *e, int *b, int *x, int *y) {
1470 if (b != NULL) *b = e->xbutton.button;
1471 if (x != NULL) *x = X2COL(e->xbutton.x);
1472 if (y != NULL) *y = Y2ROW(e->xbutton.y);
1473 term->sel.b.x = (term->sel.by < term->sel.ey ? term->sel.bx : term->sel.ex);
1474 term->sel.b.y = MIN(term->sel.by, term->sel.ey);
1475 term->sel.e.x = (term->sel.by < term->sel.ey ? term->sel.ex : term->sel.bx);
1476 term->sel.e.y = MAX(term->sel.by, term->sel.ey);
1480 static void mousereport (XEvent *e) {
1481 int x = X2COL(e->xbutton.x);
1482 int y = Y2ROW(e->xbutton.y);
1483 int button = e->xbutton.button;
1484 int state = e->xbutton.state;
1485 //char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
1486 char buf[16];
1488 if (term == NULL) return;
1489 if (x < 0 || x >= term->col || y < 0 || y >= term->row) return;
1490 sprintf(buf, "\x1b[M%c%c%c", 0, 32+x+1, 32+y+1);
1491 /* from urxvt */
1492 if (e->xbutton.type == MotionNotify) {
1493 if (!IS_SET(MODE_MOUSEMOTION) || (x == term->mouseox && y == term->mouseoy)) return;
1494 button = term->mouseob+32;
1495 term->mouseox = x;
1496 term->mouseoy = y;
1497 } else if (e->xbutton.type == ButtonRelease || button == AnyButton) {
1498 button = 3;
1499 } else {
1500 button -= Button1;
1501 if (button >= 3) button += 64-3;
1502 if (e->xbutton.type == ButtonPress) {
1503 term->mouseob = button;
1504 term->mouseox = x;
1505 term->mouseoy = y;
1508 buf[3] = 32+button+(state & ShiftMask ? 4 : 0)+(state & Mod4Mask ? 8 : 0)+(state & ControlMask ? 16 : 0);
1509 ttywrite(buf, 6);
1513 static void xfixsel (void) {
1514 if (lastSelStr != NULL) {
1515 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
1516 XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, xw.win, CurrentTime);
1517 } else {
1518 if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) == xw.win) XSetSelectionOwner(xw.dpy, XA_PRIMARY, None, CurrentTime);
1519 if (XGetSelectionOwner(xw.dpy, XA_CLIPBOARD) == xw.win) XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, None, CurrentTime);
1521 XFlush(xw.dpy);
1525 static void xsetsel (char *str) {
1526 /* register the selection for both the clipboard and the primary */
1527 if (term == NULL) return;
1528 if (term->sel.clip != NULL) free(term->sel.clip);
1529 term->sel.clip = str;
1530 if (lastSelStr != NULL) free(lastSelStr);
1531 lastSelStr = (str != NULL ? strdup(str) : NULL);
1532 xfixsel();
1533 //fprintf(stderr, "[%s]\n", str);
1537 static Line selgetlinebyy (int y) {
1538 Line l;
1540 if (y >= term->row) return NULL;
1541 if (y < 0) {
1542 if (y <= -(term->maxhistory)) return NULL;
1543 l = term->line[term->row+(-y)-1];
1544 } else {
1545 l = term->line[y];
1547 return l;
1551 static void selcopy (void) {
1552 char *str, *ptr;
1553 int x, y, bufsize, is_selected = 0;
1555 if (term == NULL || term->sel.bx == -1) {
1556 str = NULL;
1557 } else {
1558 int sy = MIN(term->sel.e.y, term->sel.b.y);
1559 int ey = MAX(term->sel.e.y, term->sel.b.y);
1562 fprintf(stderr, "bx=%d; ex=%d; by=%d; ey=%d\n", term->sel.bx, term->sel.by, term->sel.ex, term->sel.ey);
1563 fprintf(stderr, " b.x=%d; e.x=%d; b.y=%d; e.y=%d\n", term->sel.b.x, term->sel.b.y, term->sel.e.x, term->sel.e.y);
1564 fprintf(stderr, " sy=%d; ey=%d\n", sy, ey);
1566 bufsize = (term->col+1)*(ey-sy+1)*UTF_SIZ;
1567 ptr = str = malloc(bufsize);
1568 /* append every set & selected glyph to the selection */
1569 for (y = sy; y <= ey; ++y) {
1570 Line l = selgetlinebyy(y);
1572 if (y >= term->row) break;
1573 if (l == NULL) continue;
1574 for (x = 0; x < term->col; ++x) {
1575 is_selected = selected(x, y);
1576 //if (is_selected) term->line[y][x].state |= GLYPH_DIRTY;
1577 if (/*(l[x].state & GLYPH_SET) &&*/ is_selected) {
1578 int size = utf8size(l[x].c);
1580 if (size == 1 && l[x].c[0] == 0) {
1581 *ptr = ' ';
1582 } else {
1583 memcpy(ptr, l[x].c, size);
1585 ptr += size;
1588 /* \n at the end of every selected line except for the last one */
1589 if (is_selected && y < ey) *ptr++ = '\n';
1591 *ptr = 0;
1593 xsetsel(str);
1597 static void selnotify (XEvent *e) {
1598 ulong nitems, ofs, rem;
1599 int format;
1600 uchar *data;
1601 Atom type;
1602 XSelectionEvent *se = (XSelectionEvent *)e;
1603 int isutf8;
1604 int wasbrk = 0;
1606 if (term == NULL || term->cmdMode == CMDMODE_MESSAGE) return;
1607 if (se->property != XA_VT_SELECTION) return;
1608 if (se->target == XA_UTF8) {
1609 isutf8 = 1;
1610 } else if (se->target == XA_STRING) {
1611 isutf8 = 0;
1612 } else {
1613 return;
1615 if (!isutf8) return;
1616 ofs = 0;
1617 do {
1618 if (XGetWindowProperty(xw.dpy, xw.win, se->property, ofs, BUFSIZ/4, False, AnyPropertyType, &type, &format, &nitems, &rem, &data)) {
1619 fprintf(stderr, "Clipboard allocation failed\n");
1620 return;
1622 //fprintf(stderr, "nitems=%d; format=%d; rem=%d\n", (int)nitems, format, (int)rem);
1623 if (term->cmdMode != CMDMODE_NONE) {
1624 tcmdput((const char *)data, nitems*format/8);
1625 } else {
1626 if (nitems*format/8 > 0 && !wasbrk && IS_SET(MODE_BRACPASTE)) {
1627 wasbrk = 1;
1628 ttywritestr("\x1b[200~");
1630 ttywrite((const char *)data, nitems*format/8);
1632 XFree(data);
1633 /* number of 32-bit chunks returned */
1634 ofs += nitems*format/32;
1635 } while (rem > 0);
1637 if (wasbrk) ttywritestr("\x1b[201~");
1641 static void selpaste (Atom which) {
1642 if (term == NULL) return;
1643 if (XGetSelectionOwner(xw.dpy, which) != None) {
1644 XConvertSelection(xw.dpy, which, term->sel.xtarget, XA_VT_SELECTION, xw.win, CurrentTime);
1645 return;
1647 if (which == XA_PRIMARY) selpaste(XA_SECONDARY);
1648 else if (which == XA_SECONDARY) selpaste(XA_CLIPBOARD);
1652 static void selrequest (XEvent *e) {
1653 XSelectionRequestEvent *xsre;
1654 XSelectionEvent xev;
1656 if (lastSelStr == NULL) return;
1657 xsre = (XSelectionRequestEvent *)e;
1658 xev.type = SelectionNotify;
1659 xev.requestor = xsre->requestor;
1660 xev.selection = xsre->selection;
1661 xev.target = xsre->target;
1662 xev.time = xsre->time;
1663 /* reject */
1664 xev.property = None;
1665 if (xsre->target == XA_TARGETS) {
1666 /* respond with the supported type */
1667 Atom string = XA_UTF8;
1669 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_ATOM, 32, PropModeReplace, (uchar *)&string, 1);
1670 xev.property = xsre->property;
1671 } else if (xsre->target == XA_UTF8 && lastSelStr != NULL) {
1672 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_UTF8, 8, PropModeReplace, (uchar *)lastSelStr, strlen(lastSelStr));
1673 xev.property = xsre->property;
1674 } else if (xsre->target == XA_STRING && lastSelStr != NULL) {
1675 char *s = malloc(strlen(lastSelStr)*4+8);
1677 if (s != NULL) {
1678 int len = utf2loc(s, lastSelStr, strlen(lastSelStr));
1680 XChangeProperty(xsre->display, xsre->requestor, xsre->property, xsre->target, 8, PropModeReplace, (uchar *)s, len);
1681 xev.property = xsre->property;
1682 free(s);
1685 /* all done, send a notification to the listener */
1686 if (!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *)&xev)) fprintf(stderr, "Error sending SelectionNotify event\n");
1690 static void selclear (XEvent *e) {
1691 if (lastSelStr != NULL) free(lastSelStr);
1692 lastSelStr = NULL;
1693 if (term != NULL) {
1694 if (term->sel.clip != NULL) free(term->sel.clip);
1695 term->sel.clip = NULL;
1696 term->sel.mode = 0;
1697 if (term->sel.bx != 0) {
1698 term->sel.bx = -1;
1699 tfulldirt();
1700 draw(1);
1706 static void bpress (XEvent *e) {
1707 if (term == NULL) return;
1709 switch (opt_tabposition) {
1710 case 0: // bottom
1711 if (e->xbutton.y >= xw.h-xw.tabheight) return;
1712 break;
1713 case 1: // top
1714 if (e->xbutton.y < xw.tabheight) return;
1715 break;
1718 if ((e->xbutton.state&ShiftMask) != 0) {
1719 if (e->xbutton.button == Button1) {
1720 if (term->sel.bx != -1) tsetdirt(term->sel.b.y, term->sel.e.y);
1721 term->sel.mode = 1;
1722 term->sel.ex = term->sel.bx = X2COL(e->xbutton.x);
1723 term->sel.ey = term->sel.by = Y2ROW(e->xbutton.y);
1724 //fprintf(stderr, "x=%d; y=%d\n", term->sel.bx, term->sel.by);
1725 draw(1);
1726 return;
1729 if (e->xbutton.button == Button3) {
1730 term->sel.bx = -1;
1731 selcopy();
1732 draw(1);
1735 return;
1737 if (IS_SET(MODE_MOUSE)) mousereport(e);
1741 static void brelease (XEvent *e) {
1742 if (term == NULL) return;
1744 switch (opt_tabposition) {
1745 case 0: // bottom
1746 if (e->xbutton.y >= xw.h-xw.tabheight) return;
1747 break;
1748 case 1: // top
1749 if (e->xbutton.y < xw.tabheight) return;
1750 break;
1753 if ((e->xbutton.state&ShiftMask) == 0 && !term->sel.mode) {
1754 if (IS_SET(MODE_MOUSE)) mousereport(e);
1755 return;
1758 if (e->xbutton.button == Button2) {
1759 selpaste(XA_PRIMARY);
1760 } else if (e->xbutton.button == Button1) {
1761 term->sel.mode = 0;
1762 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey); // this sets sel.b and sel.e
1764 if (term->sel.bx == term->sel.ex && term->sel.by == term->sel.ey) {
1765 // single line, single char selection
1766 int now;
1768 markDirty(term->sel.ey, 2);
1769 term->sel.bx = -1;
1770 now = mclock_ticks();
1771 if (now-term->sel.tclick2 <= opt_tripleclick_timeout) {
1772 /* triple click on the line */
1773 term->sel.b.x = term->sel.bx = 0;
1774 term->sel.e.x = term->sel.ex = term->col;
1775 term->sel.b.y = term->sel.e.y = term->sel.ey;
1776 } else if (now-term->sel.tclick1 <= opt_doubleclick_timeout) {
1777 /* double click to select word */
1778 Line l = selgetlinebyy(term->sel.ey);
1780 if (l != NULL) {
1781 //FIXME: write better word selection code
1782 term->sel.bx = term->sel.ex;
1783 if (IS_GFX(l[term->sel.bx].mode)) {
1784 while (term->sel.bx > 0 && IS_GFX(l[term->sel.bx-1].mode)) --term->sel.bx;
1785 term->sel.b.x = term->sel.bx;
1786 while (term->sel.ex < term->col-1 && IS_GFX(l[term->sel.ex+1].mode)) ++term->sel.ex;
1787 } else {
1788 while (term->sel.bx > 0 && !IS_GFX(l[term->sel.bx-1].mode) && l[term->sel.bx-1].c[0] != ' ') --term->sel.bx;
1789 term->sel.b.x = term->sel.bx;
1790 while (term->sel.ex < term->col-1 && !IS_GFX(l[term->sel.ex+1].mode) && l[term->sel.ex+1].c[0] != ' ') ++term->sel.ex;
1792 term->sel.e.x = term->sel.ex;
1793 term->sel.b.y = term->sel.e.y = term->sel.ey;
1797 selcopy();
1798 draw(1);
1799 } else {
1800 // multiline or multichar selection
1801 selcopy();
1804 term->sel.tclick2 = term->sel.tclick1;
1805 term->sel.tclick1 = mclock_ticks();
1806 //draw(1);
1810 static void bmotion (XEvent *e) {
1811 if (term == NULL) return;
1813 switch (opt_tabposition) {
1814 case 0: // bottom
1815 if (e->xbutton.y >= xw.h-xw.tabheight) return;
1816 break;
1817 case 1: // top
1818 if (e->xbutton.y < xw.tabheight) return;
1819 break;
1822 if (term->sel.mode) {
1823 int oldey = term->sel.ey, oldex = term->sel.ex;
1825 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey); // this sets sel.b and sel.e
1826 if (oldey != term->sel.ey || oldex != term->sel.ex) {
1827 int starty = MIN(oldey, term->sel.ey);
1828 int endy = MAX(oldey, term->sel.ey);
1830 tsetdirt(starty, endy);
1831 draw(1);
1833 return;
1835 //if (IS_SET(MODE_MOUSE) && e->xbutton.button != 0) mousereport(e);
1839 ////////////////////////////////////////////////////////////////////////////////
1840 // tty init
1841 static inline void setWantRedraw (void) { if (term != NULL) term->wantRedraw = 1; }
1845 static void dump (char c) {
1846 static int col;
1848 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
1849 if (++col % 10 == 0) fprintf(stderr, "\n");
1854 static __attribute__((noreturn)) void execsh (const char *str) {
1855 char **args;
1857 if (str == NULL) {
1858 char *envshell = getenv("SHELL");
1860 DEFAULT(envshell, opt_shell);
1861 setenv("TERM", opt_term, 1);
1862 args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL};
1863 } else {
1864 int argc = 0;
1866 args = calloc(32768, sizeof(char *));
1867 if (args == NULL) exit(EXIT_FAILURE);
1868 while (*str) {
1869 const char *b;
1871 while (*str && isspace(*str)) ++str;
1872 if (!str[0]) break;
1874 b = str;
1875 while (*str && !isspace(*str)) {
1876 if (*str++ == '\\') {
1877 if (*str) ++str;
1881 args[argc] = calloc(str-b+1, 1);
1882 memcpy(args[argc], b, str-b);
1885 FILE *fo = fopen("z.log", "a");
1886 fprintf(fo, "%d: [%s]\n", argc, args[argc]);
1887 fclose(fo);
1890 ++argc;
1892 if (argc < 1) exit(EXIT_FAILURE);
1894 execvp(args[0], args);
1895 exit(EXIT_FAILURE);
1899 static int ttynew (Term *term) {
1900 int m, s;
1901 struct winsize w = {term->row, term->col, 0, 0};
1902 static int signalset = 0;
1904 if (openpty(&m, &s, NULL, NULL, &w) < 0) die("openpty failed: %s", SERRNO);
1905 term->cmdfd = m;
1906 ttyresize();
1907 term->cmdfd = -1;
1908 switch (term->pid = fork()) {
1909 case -1: /* error */
1910 fprintf(stderr, "fork failed");
1911 return -1;
1912 case 0: /* child */
1913 setsid(); /* create a new process group */
1914 dup2(s, STDIN_FILENO);
1915 dup2(s, STDOUT_FILENO);
1916 dup2(s, STDERR_FILENO);
1917 if (ioctl(s, TIOCSCTTY, NULL) < 0) die("ioctl TIOCSCTTY failed: %s", SERRNO);
1918 close(s);
1919 close(m);
1920 execsh(term->execcmd);
1921 break;
1922 default: /* master */
1923 close(s);
1924 term->cmdfd = m;
1925 term->dead = 0;
1926 ttyresize();
1927 if (!signalset) { signalset = 1; signal(SIGCHLD, sigchld); }
1928 break;
1930 return 0;
1934 ////////////////////////////////////////////////////////////////////////////////
1935 // tty r/w
1936 static int ttycanread (void) {
1937 for (;;) {
1938 fd_set rfd;
1939 struct timeval timeout = {0};
1941 if (term->dead || term->cmdfd < 0) return 0;
1942 FD_ZERO(&rfd);
1943 FD_SET(term->cmdfd, &rfd);
1944 if (select(term->cmdfd+1, &rfd, NULL, NULL, &timeout) < 0) {
1945 if (errno == EINTR) continue;
1946 die("select failed: %s", SERRNO);
1948 if (FD_ISSET(term->cmdfd, &rfd)) return 1;
1949 break;
1951 return 0;
1955 static int ttycanwrite (void) {
1956 for (;;) {
1957 fd_set wfd;
1958 struct timeval timeout = {0};
1960 if (term->dead || term->cmdfd < 0) return 0;
1961 FD_ZERO(&wfd);
1962 FD_SET(term->cmdfd, &wfd);
1963 if (select(term->cmdfd+1, NULL, &wfd, NULL, &timeout) < 0) {
1964 if (errno == EINTR) continue;
1965 die("select failed: %s", SERRNO);
1967 if (FD_ISSET(term->cmdfd, &wfd)) return 1;
1968 break;
1970 return 0;
1974 #ifdef DUMP_IO
1975 static void wrstr (const char *s, int len) {
1976 if (s == NULL) return;
1977 while (len-- > 0) {
1978 unsigned char c = (unsigned char)(*s++);
1980 if (c < 32) fprintf(stderr, "{%u}", c); else fwrite(&c, 1, 1, stderr);
1983 #endif
1986 static void ttyread (void) {
1987 char *ptr;
1988 int left;
1990 /* append read bytes to unprocessed bytes */
1991 if (term == NULL || term->dead || term->cmdfd < 0) return;
1992 #ifdef DUMP_PROG_OUTPUT
1993 term->xobuflen = term->obuflen;
1994 #endif
1995 left = OBUFSIZ-term->obuflen;
1996 //dlogf("0: ttyread before: free=%d, used=%d", left, term->obuflen);
1997 while (left > 0 && ttycanread()) {
1998 int ret;
2000 //if ((ret = read(term->cmdfd, term->obuf+term->obuflen, 1)) < 0) die("Couldn't read from shell: %s", SERRNO);
2001 if ((ret = read(term->cmdfd, term->obuf+term->obuflen, left)) < 0) {
2002 //fprintf(stderr, "Warning: couldn't read from tty #%d: %s\n", termidx, SERRNO);
2003 break;
2005 term->obuflen += ret;
2006 left -= ret;
2008 //dlogf("1: ttyread after: free=%d, used=%d", left, term->obuflen);
2009 /* process every complete utf8 char */
2010 #ifdef DUMP_PROG_OUTPUT
2012 FILE *fo = fopen("zlogo.log", "ab");
2013 if (fo) {
2014 fwrite(term->obuf+term->xobuflen, term->obuflen-term->xobuflen, 1, fo);
2015 fclose(fo);
2018 #endif
2019 ptr = term->obuf;
2020 if (term->needConv) {
2021 // need conversion from locale to utf-8
2022 //fprintf(stderr, "buf: %d bytes\n", term->obuflen);
2023 while (term->obuflen > 0) {
2024 char obuf[UTF_SIZ+1];
2025 int len;
2027 len = loc2utf(obuf, ptr, 1);
2028 #ifdef DUMP_IO_READ
2030 fprintf(stderr, "rdc: [");
2031 wrstr(ptr, 1);
2032 fprintf(stderr, "] --> [");
2033 wrstr(obuf, len);
2034 fprintf(stderr, "]\n");
2035 fflush(stderr);
2037 #endif
2038 if (len > 0) {
2039 obuf[len] = 0;
2040 tputc(obuf);
2042 ++ptr;
2043 --term->obuflen;
2045 term->obuflen = 0;
2046 } else {
2047 // don't do any conversion
2048 while (term->obuflen >= UTF_SIZ || isfullutf8(ptr, term->obuflen)) {
2049 ulong utf8c;
2050 char s[UTF_SIZ+1];
2051 int charsize = utf8decode(ptr, &utf8c); /* returns size of utf8 char in bytes */
2052 int len;
2054 len = utf8encode(utf8c, s);
2055 #ifdef DUMP_IO_READ
2057 fprintf(stderr, "rdx: [");
2058 wrstr(s, len);
2059 fprintf(stderr, "]\n");
2060 fflush(stderr);
2062 #endif
2063 if (len > 0) {
2064 s[len] = 0;
2065 tputc(s);
2067 ptr += charsize;
2068 term->obuflen -= charsize;
2070 //dlogf("2: ttyread afterproc: used=%d", term->obuflen);
2072 /* keep any uncomplete utf8 char for the next call */
2073 if (term->obuflen > 0) memmove(term->obuf, ptr, term->obuflen);
2077 static void ttyflushwrbuf (void) {
2078 if (term == NULL || term->dead || term->cmdfd < 0) return;
2079 if (term->wrbufpos >= term->wrbufused) {
2080 term->wrbufpos = term->wrbufused = 0;
2081 return;
2083 //dlogf("0: ttyflushwrbuf before: towrite=%d", term->wrbufused-term->wrbufpos);
2084 while (term->wrbufpos < term->wrbufused && ttycanwrite()) {
2085 int ret;
2087 if ((ret = write(term->cmdfd, term->wrbuf+term->wrbufpos, term->wrbufused-term->wrbufpos)) == -1) {
2088 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2090 term->wrbufpos += ret;
2092 if (term->wrbufpos > 0) {
2093 int left = term->wrbufused-term->wrbufpos;
2095 if (left < 1) {
2096 // write buffer is empty
2097 term->wrbufpos = term->wrbufused = 0;
2098 } else {
2099 memmove(term->wrbuf, term->wrbuf+term->wrbufpos, left);
2100 term->wrbufpos = 0;
2101 term->wrbufused = left;
2104 //dlogf("1: ttyflushwrbuf after: towrite=%d", term->wrbufused-term->wrbufpos);
2108 // convert char to locale and write it
2109 static void ttywriterawchar (const char *s, int len) {
2110 char loc[16];
2111 int clen;
2113 if (s == NULL || len < 1) return;
2114 if (term->needConv) {
2115 if ((clen = utf2loc(loc, s, len)) < 1) return;
2116 } else {
2117 if ((clen = utf8size(s)) < 1) return;
2118 memmove(loc, s, clen);
2120 #ifdef DUMP_IO_WRITE
2122 fprintf(stderr, "wrc: [");
2123 wrstr(s, len);
2124 fprintf(stderr, "] --> [");
2125 wrstr(loc, clen);
2126 fprintf(stderr, "]\n");
2127 fflush(stderr);
2129 #endif
2131 while (term->wrbufused+clen >= term->wrbufsize) {
2132 //FIXME: make write buffer dynamic?
2133 // force write at least one char
2134 //dlogf("ttywrite: forced write");
2135 if (write(term->cmdfd, term->wrbuf+term->wrbufpos, 1) == -1) {
2136 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2137 } else {
2138 ++term->wrbufpos;
2140 ttyflushwrbuf(); // make room for char
2142 memcpy(term->wrbuf+term->wrbufused, loc, clen);
2143 term->wrbufused += clen;
2147 static void ttywrite (const char *s, size_t n) {
2148 if (term == NULL || term->dead || term->cmdfd < 0) return;
2149 #ifdef DUMP_PROG_INPUT
2150 if (s != NULL && n > 0) {
2151 FILE *fo = fopen("zlogw.log", "ab");
2152 if (fo) {
2153 fwrite(s, n, 1, fo);
2154 fclose(fo);
2157 #endif
2158 //ttyflushwrbuf();
2159 if (s != NULL && n > 0) {
2160 while (n > 0) {
2161 unsigned char c = (unsigned char)(s[0]);
2163 if (term->ubufpos > 0 && isfullutf8(term->ubuf, term->ubufpos)) {
2164 // have complete char
2165 ttywriterawchar(term->ubuf, term->ubufpos);
2166 term->ubufpos = 0;
2167 continue;
2170 if (term->ubufpos == 0) {
2171 // new char
2172 if (c < 128) {
2173 ttywriterawchar(s, 1);
2174 } else if ((c&0xc0) == 0xc0) {
2175 // new utf-8 char
2176 term->ubuf[term->ubufpos++] = *s;
2177 } else {
2178 // ignore unsynced utf-8
2180 ++s;
2181 --n;
2182 continue;
2184 // char continues
2185 if (c < 128 || term->ubufpos >= UTF_SIZ || (c&0xc0) == 0xc0) {
2186 // discard previous utf-8, it's bad
2187 term->ubufpos = 0;
2188 continue;
2190 // collect
2191 term->ubuf[term->ubufpos++] = *s;
2192 ++s;
2193 --n;
2194 if (isfullutf8(term->ubuf, term->ubufpos)) {
2195 // have complete char
2196 ttywriterawchar(term->ubuf, term->ubufpos);
2197 term->ubufpos = 0;
2201 ttyflushwrbuf();
2205 ////////////////////////////////////////////////////////////////////////////////
2206 // tty resize ioctl
2207 static void ttyresize (void) {
2208 struct winsize w;
2210 if (term != NULL && term->cmdfd >= 0) {
2211 w.ws_row = term->row;
2212 w.ws_col = term->col;
2213 w.ws_xpixel = w.ws_ypixel = 0;
2214 if (ioctl(term->cmdfd, TIOCSWINSZ, &w) < 0) fprintf(stderr, "Warning: couldn't set window size: %s\n", SERRNO);
2215 setWantRedraw();
2220 ////////////////////////////////////////////////////////////////////////////////
2221 // tty utilities
2222 static void csidump (void) {
2223 printf("ESC");
2224 for (int f = 1; f < term->escseq.len; ++f) {
2225 uint c = (term->escseq.buf[f]&0xff);
2227 if (isprint(c)) putchar(c);
2228 else if (c == '\n') printf("(\\n)");
2229 else if (c == '\r') printf("(\\r)");
2230 else if (c == 0x1b) printf("(\\e)");
2231 else printf("(%02x)", c);
2233 putchar('\n');
2237 static void tsetdirt (int top, int bot) {
2238 LIMIT(top, 0, term->row-1);
2239 LIMIT(bot, 0, term->row-1);
2240 for (int y = top; y <= bot; ++y) markDirty(y, 2);
2244 static void tfulldirt (void) {
2245 tsetdirt(0, term->row-1);
2249 static void tmoveto (int x, int y) {
2250 LIMIT(x, 0, term->col-1);
2251 LIMIT(y, 0, term->row-1);
2252 term->c.state &= ~CURSOR_WRAPNEXT;
2253 if (term->c.x != x || term->c.y != y) {
2254 term->c.x = x;
2255 term->c.y = y;
2256 setWantRedraw();
2261 static void tclearregion (int x1, int y1, int x2, int y2) {
2262 int temp;
2264 if (x1 > x2) { temp = x1; x1 = x2; x2 = temp; }
2265 if (y1 > y2) { temp = y1; y1 = y2; y2 = temp; }
2266 LIMIT(x1, 0, term->col-1);
2267 LIMIT(x2, 0, term->col-1);
2268 LIMIT(y1, 0, term->row-1);
2269 LIMIT(y2, 0, term->row-1);
2270 for (int y = y1; y <= y2; ++y) {
2271 markDirty(y, (x1 <= 0 && x2 >= term->col-1) ? 2 : 1);
2272 for (int x = x1; x <= x2; ++x) {
2273 term->line[y][x].fg = term->c.attr.fg;
2274 term->line[y][x].bg = term->c.attr.bg;
2275 term->line[y][x].state = GLYPH_DIRTY;
2276 term->line[y][x].mode = ATTR_NULL|(term->c.attr.mode&(ATTR_DEFFG|ATTR_DEFBG));
2277 term->line[y][x].c[0] = ' ';
2278 if (selected(x, y)) selhide();
2284 static void tcursor (int mode) {
2285 if (mode == CURSOR_SAVE) {
2286 term->csaved = term->c;
2287 } else if (mode == CURSOR_LOAD) {
2288 term->c = term->csaved;
2289 tmoveto(term->c.x, term->c.y);
2290 setWantRedraw();
2295 static void treset (void) {
2296 Glyph g;
2298 term->c = (TCursor){{
2299 .mode = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG,
2300 .fg = 0,
2301 .bg = 0
2302 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
2303 term->c.attr.fg = term->deffg;
2304 term->c.attr.bg = term->defbg;
2306 g.state = GLYPH_DIRTY;
2307 g.mode = term->c.attr.mode;
2308 g.fg = term->c.attr.fg;
2309 g.bg = term->c.attr.bg;
2310 g.c[0] = ' ';
2311 g.c[1] = 0;
2313 term->top = 0;
2314 term->bot = term->row-1;
2315 term->mode = MODE_WRAP/* | MODE_MOUSEBTN*/;
2316 //tclearregion(0, 0, term->col-1, term->row-1);
2317 for (int y = 0; y < term->row; ++y) {
2318 markDirty(y, 2);
2319 for (int x = 0; x < term->col; ++x) term->alt[y][x] = term->line[y][x] = g;
2321 for (int y = term->row; y < term->linecount; ++y) {
2322 for (int x = 0; x < term->col; ++x) term->line[y][x] = g;
2324 tcursor(CURSOR_SAVE);
2325 term->topline = 0;
2326 tfulldirt();
2330 static int tinitialize (int col, int row) {
2331 //memset(term, 0, sizeof(Term));
2332 //term->needConv = needConversion ? 1 : 0;
2333 term->wrbufsize = WBUFSIZ;
2334 term->deffg = term->deffg;
2335 term->defbg = term->defbg;
2336 term->row = row;
2337 term->col = col;
2338 term->dirty = calloc(term->row, sizeof(*term->dirty));
2339 term->maxhistory = opt_maxhistory;
2340 term->linecount = term->maxhistory+term->row;
2341 term->line = calloc(term->linecount, sizeof(Line));
2342 term->alt = calloc(term->row, sizeof(Line));
2343 for (int y = 0; y < term->linecount; ++y) term->line[y] = calloc(term->col, sizeof(Glyph));
2344 for (int y = 0; y < term->row; ++y) term->alt[y] = calloc(term->col, sizeof(Glyph));
2345 /* setup screen */
2346 treset();
2347 return 1;
2351 static void tswapscreen (void) {
2352 selhide();
2353 for (int f = 0; f < term->row; ++f) {
2354 Line t = term->line[f];
2356 term->line[f] = term->alt[f];
2357 term->alt[f] = t;
2359 term->mode ^= MODE_ALTSCREEN;
2360 tfulldirt();
2364 //FIXME: will not work for history
2365 static void selscroll (int orig, int n) {
2366 if (term->sel.bx == -1) return;
2368 if (BETWEEN(term->sel.by, orig, term->bot) || BETWEEN(term->sel.ey, orig, term->bot)) {
2369 if ((term->sel.by += n) > term->bot || (term->sel.ey += n) < term->top) {
2370 term->sel.bx = -1;
2371 return;
2373 if (term->sel.by < term->top) {
2374 term->sel.by = term->top;
2375 term->sel.bx = 0;
2377 if (term->sel.ey > term->bot) {
2378 term->sel.ey = term->bot;
2379 term->sel.ex = term->col;
2381 term->sel.b.y = term->sel.by, term->sel.b.x = term->sel.bx;
2382 term->sel.e.y = term->sel.ey, term->sel.e.x = term->sel.ex;
2387 static void tscrolldown (int orig, int n) {
2388 Line temp;
2390 LIMIT(n, 0, term->bot-orig+1);
2391 //fprintf(stderr, "tscrolldown(%d, %d)\n", orig, n);
2392 tclearregion(0, term->bot-n+1, term->col-1, term->bot);
2393 for (int f = term->bot; f >= orig+n; --f) {
2394 temp = term->line[f];
2395 term->line[f] = term->line[f-n];
2396 term->line[f-n] = temp;
2397 markDirty(f, 2);
2398 markDirty(f-n, 2);
2400 selscroll(orig, n);
2404 static void tscrollup (int orig, int n) {
2405 Line temp;
2407 if (term == NULL) return;
2408 LIMIT(n, 0, term->bot-orig+1);
2409 if (n < 1) return;
2410 //fprintf(stderr, "tscrollup(%d, %d)\n", orig, n);
2411 if (!IS_SET(MODE_ALTSCREEN) && n == 1 && orig == 0 && term->bot >= term->row-1 && term->maxhistory > 0) {
2412 //FIXME: move selection instead of resetting
2413 Line l = term->line[term->linecount-1];
2415 for (int f = term->linecount-1; f > term->row; --f) term->line[f] = term->line[f-1];
2416 term->line[term->row] = l;
2417 for (int x = 0; x < term->col; ++x) l[x] = term->line[0][x];
2418 if (term->sel.clip != NULL) {
2419 //FIXME: scroll selection
2420 selhide();
2424 tclearregion(0, orig, term->col-1, orig+n-1);
2426 for (int f = orig; f <= term->bot-n; ++f) {
2427 temp = term->line[f];
2428 term->line[f] = term->line[f+n];
2429 term->line[f+n] = temp;
2430 markDirty(f, 2);
2431 markDirty(f+n, 2);
2433 selscroll(orig, -n);
2437 static void tnewline (int first_col) {
2438 int y = term->c.y;
2440 if (y == term->bot) tscrollup(term->top, 1); else ++y;
2441 tmoveto(first_col ? 0 : term->c.x, y);
2445 static void csiparse (void) {
2446 const char *p = term->escseq.buf;
2448 term->escseq.narg = 0;
2449 if (*p == '?') { term->escseq.priv = 1; ++p; }
2450 while (p < term->escseq.buf+term->escseq.len) {
2451 int n = term->escseq.arg[term->escseq.narg];
2453 for (; *p && isdigit(*p); ++p) n = n*10+(p[0]-'0');
2454 term->escseq.arg[term->escseq.narg] = n;
2456 if (*p == ';' && term->escseq.narg+1 < ESC_ARG_SIZ) {
2457 ++term->escseq.narg;
2458 ++p;
2459 } else {
2460 term->escseq.mode = *p;
2461 ++term->escseq.narg;
2462 break;
2468 static void tsetchar (const char *c) {
2469 char ub[UTF_SIZ+1];
2470 int rev = 0, gfx = 0;
2471 int x = term->c.x, y = term->c.y;
2473 if (!term->needConv && unimap != NULL) {
2474 ulong cc;
2476 utf8decode(c, &cc);
2477 if (cc >= 0 && cc <= 65535) {
2478 ushort uc = unimap[cc];
2480 if (uc) {
2481 if (uc == 127) {
2482 // inversed space
2483 rev = 1;
2484 ub[0] = ' ';
2485 //ub[1] = 0;
2486 } else {
2487 if (uc&0x8000) {
2488 ub[0] = (uc&0x7f);
2489 gfx = 1;
2490 } else {
2491 //ub[0] = uc&0x7f;
2492 utf8encode(uc, ub);
2495 c = ub;
2500 if (rev || gfx || (term->line[y][x].state & GLYPH_SET) == 0 || ATTRCMP(term->line[y][x], term->c.attr)) {
2501 term->line[y][x] = term->c.attr;
2502 if (rev) term->line[y][x].mode ^= ATTR_REVERSE;
2503 if (gfx) term->line[y][x].mode |= ATTR_GFX;
2504 } else {
2505 int clen = utf8size(c), olen = utf8size(term->line[y][x].c);
2507 if (clen < 1 || (clen == olen && memcmp(c, term->line[y][x].c, clen) == 0)) return;
2510 markDirty(y, 1);
2512 term->line[y][x] = term->c.attr;
2513 if (rev) term->line[y][x].mode ^= ATTR_REVERSE;
2514 if (gfx) {
2515 term->line[y][x].mode &= ~(ATTR_GFX|ATTR_GFX1);
2516 term->line[y][x].mode |= (term->line[y][x].mode&ATTR_G1) ? ATTR_GFX1 : ATTR_GFX;
2519 term->line[y][x].state |= (GLYPH_SET | GLYPH_DIRTY);
2520 memmove(term->line[y][x].c, c, UTF_SIZ);
2522 if (IS_GFX(term->line[y][x].mode)) {
2523 unsigned char c = (unsigned char)(term->line[y][x].c[0]);
2525 if (c > 95 && c < 128) term->line[y][x].c[0] -= 95;
2526 else if (c > 127) term->line[y][x].c[0] = ' ';
2528 if (term->sel.clip != NULL && selected(x, y)) {
2529 selhide();
2531 //dlogf("tsetchar(%d,%d): [%c] (%d); dirty=%d\n", x, y, c[0], term->wantRedraw, term->dirty[y]);
2535 static void tdeletechar (int n) {
2536 int src = term->c.x+n;
2537 int dst = term->c.x;
2538 int size = term->col-src;
2540 markDirty(term->c.y, 2);
2541 if (src >= term->col) {
2542 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2543 } else {
2544 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2545 tclearregion(term->col-n, term->c.y, term->col-1, term->c.y);
2550 static void tinsertblank (int n) {
2551 int src = term->c.x;
2552 int dst = src+n;
2553 int size = term->col-dst;
2555 markDirty(term->c.y, 2);
2556 if (dst >= term->col) {
2557 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2558 } else {
2559 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2560 tclearregion(src, term->c.y, dst-1, term->c.y);
2565 static void tinsertblankline (int n) {
2566 if (term->c.y < term->top || term->c.y > term->bot) return;
2567 tscrolldown(term->c.y, n);
2571 static void tdeleteline (int n) {
2572 if (term->c.y < term->top || term->c.y > term->bot) return;
2573 tscrollup(term->c.y, n);
2577 static void tsetattr (int *attr, int l) {
2578 for (int f = 0; f < l; ++f) {
2579 switch (attr[f]) {
2580 case 0:
2581 term->c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
2582 term->c.attr.mode |= ATTR_DEFFG | ATTR_DEFBG;
2583 term->c.attr.fg = term->deffg;
2584 term->c.attr.bg = term->defbg;
2585 break;
2586 case 1:
2587 term->c.attr.mode |= ATTR_BOLD;
2588 break;
2589 case 4:
2590 term->c.attr.mode |= ATTR_UNDERLINE;
2591 break;
2592 case 7:
2593 term->c.attr.mode |= ATTR_REVERSE;
2594 break;
2595 case 22:
2596 term->c.attr.mode &= ~ATTR_BOLD;
2597 break;
2598 case 24:
2599 term->c.attr.mode &= ~ATTR_UNDERLINE;
2600 break;
2601 case 27:
2602 term->c.attr.mode &= ~ATTR_REVERSE;
2603 break;
2604 case 38:
2605 if (f+2 < l && attr[f+1] == 5) {
2606 f += 2;
2607 if (BETWEEN(attr[f], 0, 255)) {
2608 term->c.attr.fg = attr[f];
2609 term->c.attr.mode &= ~ATTR_DEFFG;
2610 } else {
2611 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[f]);
2613 } else {
2614 //fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2615 term->c.attr.fg = term->deffg;
2616 term->c.attr.mode |= ATTR_DEFFG;
2618 break;
2619 case 39:
2620 term->c.attr.fg = term->deffg;
2621 term->c.attr.mode |= ATTR_DEFFG;
2622 break;
2623 case 48:
2624 if (f+2 < l && attr[f+1] == 5) {
2625 f += 2;
2626 if (BETWEEN(attr[f], 0, 255)) {
2627 term->c.attr.bg = attr[f];
2628 term->c.attr.mode &= ~ATTR_DEFBG;
2629 } else {
2630 fprintf(stderr, "erresc: bad bgcolor %d\n", attr[f]);
2632 } else {
2633 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2635 break;
2636 case 49:
2637 term->c.attr.bg = term->defbg;
2638 term->c.attr.mode |= ATTR_DEFBG;
2639 break;
2640 default:
2641 if (BETWEEN(attr[f], 30, 37)) { term->c.attr.fg = attr[f]-30; term->c.attr.mode &= ~ATTR_DEFFG; }
2642 else if (BETWEEN(attr[f], 40, 47)) { term->c.attr.bg = attr[f]-40; term->c.attr.mode &= ~ATTR_DEFBG; }
2643 else if (BETWEEN(attr[f], 90, 97)) { term->c.attr.fg = attr[f]-90+8; term->c.attr.mode &= ~ATTR_DEFFG; }
2644 else if (BETWEEN(attr[f], 100, 107)) { term->c.attr.bg = attr[f]-100+8; term->c.attr.mode &= ~ATTR_DEFBG; }
2645 else { fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]); csidump(); }
2646 break;
2652 static void tsetscroll (int t, int b) {
2653 int temp;
2655 LIMIT(t, 0, term->row-1);
2656 LIMIT(b, 0, term->row-1);
2657 if (t > b) {
2658 temp = t;
2659 t = b;
2660 b = temp;
2662 term->top = t;
2663 term->bot = b;
2667 ////////////////////////////////////////////////////////////////////////////////
2668 // esc processing
2669 static void csihandle (void) {
2670 switch (term->escseq.mode) {
2671 case '@': /* ICH -- Insert <n> blank char */
2672 DEFAULT(term->escseq.arg[0], 1);
2673 tinsertblank(term->escseq.arg[0]);
2674 break;
2675 case 'A': /* CUU -- Cursor <n> Up */
2676 case 'e':
2677 DEFAULT(term->escseq.arg[0], 1);
2678 tmoveto(term->c.x, term->c.y-term->escseq.arg[0]);
2679 break;
2680 case 'B': /* CUD -- Cursor <n> Down */
2681 DEFAULT(term->escseq.arg[0], 1);
2682 tmoveto(term->c.x, term->c.y+term->escseq.arg[0]);
2683 break;
2684 case 'C': /* CUF -- Cursor <n> Forward */
2685 case 'a':
2686 DEFAULT(term->escseq.arg[0], 1);
2687 tmoveto(term->c.x+term->escseq.arg[0], term->c.y);
2688 break;
2689 case 'D': /* CUB -- Cursor <n> Backward */
2690 DEFAULT(term->escseq.arg[0], 1);
2691 tmoveto(term->c.x-term->escseq.arg[0], term->c.y);
2692 break;
2693 case 'E': /* CNL -- Cursor <n> Down and first col */
2694 DEFAULT(term->escseq.arg[0], 1);
2695 tmoveto(0, term->c.y+term->escseq.arg[0]);
2696 break;
2697 case 'F': /* CPL -- Cursor <n> Up and first col */
2698 DEFAULT(term->escseq.arg[0], 1);
2699 tmoveto(0, term->c.y-term->escseq.arg[0]);
2700 break;
2701 case 'G': /* CHA -- Move to <col> */
2702 case '`': /* XXX: HPA -- same? */
2703 DEFAULT(term->escseq.arg[0], 1);
2704 tmoveto(term->escseq.arg[0]-1, term->c.y);
2705 break;
2706 case 'H': /* CUP -- Move to <row> <col> */
2707 case 'f': /* XXX: HVP -- same? */
2708 DEFAULT(term->escseq.arg[0], 1);
2709 DEFAULT(term->escseq.arg[1], 1);
2710 tmoveto(term->escseq.arg[1]-1, term->escseq.arg[0]-1);
2711 break;
2712 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
2713 case 'J': /* ED -- Clear screen */
2714 term->sel.bx = -1;
2715 switch (term->escseq.arg[0]) {
2716 case 0: /* below */
2717 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2718 if (term->c.y < term->row-1) tclearregion(0, term->c.y+1, term->col-1, term->row-1);
2719 break;
2720 case 1: /* above */
2721 if (term->c.y > 1) tclearregion(0, 0, term->col-1, term->c.y-1);
2722 tclearregion(0, term->c.y, term->c.x, term->c.y);
2723 break;
2724 case 2: /* all */
2725 tclearregion(0, 0, term->col-1, term->row-1);
2726 break;
2727 default:
2728 goto unknown;
2730 break;
2731 case 'K': /* EL -- Clear line */
2732 switch (term->escseq.arg[0]) {
2733 case 0: /* right */
2734 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2735 break;
2736 case 1: /* left */
2737 tclearregion(0, term->c.y, term->c.x, term->c.y);
2738 break;
2739 case 2: /* all */
2740 tclearregion(0, term->c.y, term->col-1, term->c.y);
2741 break;
2743 break;
2744 case 'S': /* SU -- Scroll <n> line up */
2745 DEFAULT(term->escseq.arg[0], 1);
2746 tscrollup(term->top, term->escseq.arg[0]);
2747 break;
2748 case 'T': /* SD -- Scroll <n> line down */
2749 DEFAULT(term->escseq.arg[0], 1);
2750 tscrolldown(term->top, term->escseq.arg[0]);
2751 break;
2752 case 'L': /* IL -- Insert <n> blank lines */
2753 DEFAULT(term->escseq.arg[0], 1);
2754 tinsertblankline(term->escseq.arg[0]);
2755 break;
2756 case 'l': /* RM -- Reset Mode */
2757 if (term->escseq.priv) {
2758 switch (term->escseq.arg[0]) {
2759 case 1: // 1001 for xterm compatibility
2760 DUMP_KEYPAD_SWITCH("1", "OFF");
2761 term->mode &= ~MODE_APPKEYPAD;
2762 break;
2763 case 5: /* DECSCNM -- Remove reverse video */
2764 if (IS_SET(MODE_REVERSE)) {
2765 term->mode &= ~MODE_REVERSE;
2766 tfulldirt();
2768 break;
2769 case 7: /* autowrap off */
2770 term->mode &= ~MODE_WRAP;
2771 break;
2772 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
2773 break;
2774 case 20: /* non-standard code? */
2775 term->mode &= ~MODE_CRLF;
2776 break;
2777 case 25: /* hide cursor */
2778 if ((term->c.state&CURSOR_HIDE) == 0) {
2779 term->c.state |= CURSOR_HIDE;
2780 term->wantRedraw = 1;
2782 break;
2783 case 1000: /* disable X11 xterm mouse reporting */
2784 term->mode &= ~MODE_MOUSEBTN;
2785 break;
2786 case 1002:
2787 term->mode &= ~MODE_MOUSEMOTION;
2788 break;
2789 case 1004:
2790 term->mode &= ~MODE_FOCUSEVT;
2791 break;
2792 case 1049: /* = 1047 and 1048 */
2793 case 47:
2794 case 1047:
2795 if (IS_SET(MODE_ALTSCREEN)) {
2796 tclearregion(0, 0, term->col-1, term->row-1);
2797 tswapscreen();
2799 if (term->escseq.arg[0] != 1049) break;
2800 case 1048:
2801 tcursor(CURSOR_LOAD);
2802 break;
2803 case 2004: /* reset bracketed paste mode */
2804 term->mode &= ~MODE_BRACPASTE;
2805 break;
2806 default:
2807 goto unknown;
2809 } else {
2810 switch (term->escseq.arg[0]) {
2811 case 3:
2812 term->mode &= ~MODE_DISPCTRL;
2813 break;
2814 case 4:
2815 term->mode &= ~MODE_INSERT;
2816 break;
2817 default:
2818 goto unknown;
2821 break;
2822 case 'M': /* DL -- Delete <n> lines */
2823 DEFAULT(term->escseq.arg[0], 1);
2824 tdeleteline(term->escseq.arg[0]);
2825 break;
2826 case 'X': /* ECH -- Erase <n> char */
2827 DEFAULT(term->escseq.arg[0], 1);
2828 tclearregion(term->c.x, term->c.y, term->c.x + term->escseq.arg[0], term->c.y);
2829 break;
2830 case 'P': /* DCH -- Delete <n> char */
2831 DEFAULT(term->escseq.arg[0], 1);
2832 tdeletechar(term->escseq.arg[0]);
2833 break;
2834 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
2835 case 'd': /* VPA -- Move to <row> */
2836 DEFAULT(term->escseq.arg[0], 1);
2837 tmoveto(term->c.x, term->escseq.arg[0]-1);
2838 break;
2839 case 'h': /* SM -- Set terminal mode */
2840 if (term->escseq.priv) {
2841 switch (term->escseq.arg[0]) {
2842 case 1:
2843 DUMP_KEYPAD_SWITCH("1", "ON");
2844 term->mode |= MODE_APPKEYPAD;
2845 break;
2846 case 5: /* DECSCNM -- Reverve video */
2847 if (!IS_SET(MODE_REVERSE)) {
2848 term->mode |= MODE_REVERSE;
2849 tfulldirt();
2851 break;
2852 case 7:
2853 term->mode |= MODE_WRAP;
2854 break;
2855 case 20:
2856 term->mode |= MODE_CRLF;
2857 break;
2858 case 12: /* att610 -- Start blinking cursor (IGNORED) */
2859 /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
2860 if (term->escseq.narg > 1 && term->escseq.arg[1] != 25) break;
2861 case 25:
2862 if ((term->c.state&CURSOR_HIDE) != 0) {
2863 term->c.state &= ~CURSOR_HIDE;
2864 term->wantRedraw = 1;
2866 break;
2867 case 1000: /* 1000,1002: enable xterm mouse report */
2868 term->mode |= MODE_MOUSEBTN;
2869 break;
2870 case 1002:
2871 term->mode |= MODE_MOUSEMOTION;
2872 break;
2873 case 1004:
2874 term->mode |= MODE_FOCUSEVT;
2875 break;
2876 case 1049: /* = 1047 and 1048 */
2877 case 47:
2878 case 1047:
2879 if (IS_SET(MODE_ALTSCREEN)) tclearregion(0, 0, term->col-1, term->row-1); else tswapscreen();
2880 if (term->escseq.arg[0] != 1049) break;
2881 case 1048:
2882 tcursor(CURSOR_SAVE);
2883 break;
2884 case 2004: /* set bracketed paste mode */
2885 term->mode |= MODE_BRACPASTE;
2886 break;
2887 default: goto unknown;
2889 } else {
2890 switch (term->escseq.arg[0]) {
2891 case 3:
2892 term->mode |= MODE_DISPCTRL;
2893 break;
2894 case 4:
2895 term->mode |= MODE_INSERT;
2896 break;
2897 default:
2898 goto unknown;
2901 break;
2902 case 'm': /* SGR -- Terminal attribute (color) */
2903 tsetattr(term->escseq.arg, term->escseq.narg);
2904 break;
2905 case 'n':
2906 if (!term->escseq.priv) {
2907 switch (term->escseq.arg[0]) {
2908 case 6: { /* cursor position report */
2909 char buf[32];
2911 sprintf(buf, "\x1b[%d;%dR", term->c.x+1, term->c.y+1);
2912 ttywritestr(buf);
2913 } break;
2916 break;
2917 case 'r': /* DECSTBM -- Set Scrolling Region */
2918 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
2919 // xterm compatibility
2920 DUMP_KEYPAD_SWITCH("1001", "OFF");
2921 term->mode &= ~MODE_APPKEYPAD;
2922 } else if (term->escseq.priv) {
2923 goto unknown;
2924 } else {
2925 DEFAULT(term->escseq.arg[0], 1);
2926 DEFAULT(term->escseq.arg[1], term->row);
2927 tsetscroll(term->escseq.arg[0]-1, term->escseq.arg[1]-1);
2928 tmoveto(0, 0);
2930 break;
2931 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
2932 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
2933 // xterm compatibility
2934 DUMP_KEYPAD_SWITCH("1001", "ON");
2935 term->mode |= MODE_APPKEYPAD;
2936 } else {
2937 tcursor(CURSOR_SAVE);
2939 break;
2940 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
2941 tcursor(CURSOR_LOAD);
2942 break;
2943 default:
2944 unknown:
2945 fprintf(stderr, "erresc: unknown csi ");
2946 csidump();
2947 break;
2952 static void csireset (void) {
2953 memset(&term->escseq, 0, sizeof(term->escseq));
2957 static void tputtab (void) {
2958 int space = opt_tabsize-term->c.x%opt_tabsize;
2960 if (space > 0) tmoveto(term->c.x+space, term->c.y);
2964 ////////////////////////////////////////////////////////////////////////////////
2965 // put char to output buffer or process command
2967 // return 1 if this was control character
2968 // return -1 if this should break esape sequence
2969 static int tputc_ctrl (char ascii) {
2970 int res = 1;
2972 if (term->esc&ESC_TITLE) return 0;
2974 switch (ascii) {
2975 case '\t': tputtab(); break;
2976 case '\b': tmoveto(term->c.x-1, term->c.y); break;
2977 case '\r': tmoveto(0, term->c.y); break;
2978 case '\f': case '\n': case '\v': tnewline(IS_SET(MODE_CRLF)); break; /* go to first col if the mode is set */
2979 case '\a': if (!(xw.state & WIN_FOCUSED)) xseturgency(1); if (term->audiblebell) XBell(xw.dpy, 100); break;
2980 case 14: term->c.attr.mode |= ATTR_G1; break;
2981 case 15: term->c.attr.mode &= ~ATTR_G1; break;
2982 case 0x18: case 0x1a: res = -1; break; // do nothing, interrupt current escape sequence
2983 case 127: break; // ignore it
2984 case '\033': csireset(); term->esc = ESC_START; break;
2985 //case 0x9b: csireset(); term->esc = ESC_START | ESC_CSI; break;
2986 default: res = 0; break;
2988 return res;
2992 static void tputc (const char *c) {
2993 char ascii = *c;
2994 int ctl = tputc_ctrl(ascii);
2996 if (ctl > 0) return; // control char; should not break escape sequence
2997 if (ctl < 0) {
2998 // control char; should break escape sequence
2999 term->esc = 0;
3000 return;
3002 //dlogf("tputc: [%c]\n", c[0]);
3003 if (term->esc & ESC_START) {
3004 if (term->esc & ESC_CSI) {
3005 term->escseq.buf[term->escseq.len++] = ascii;
3006 if (BETWEEN(ascii, 0x40, 0x7E) || term->escseq.len >= ESC_BUF_SIZ) {
3007 term->esc = 0;
3008 csiparse();
3009 csihandle();
3011 } else if (term->esc & ESC_OSC) {
3012 /* TODO: handle other OSC */
3013 if (ascii == ';') {
3014 term->title[0] = 0;
3015 term->titlelen = 0;
3016 term->esc = ESC_START | ESC_TITLE;
3017 //updateTabBar = 1;
3019 } else if (term->esc & ESC_TITLE) {
3020 int len = utf8size(c);
3022 if (ascii == '\a' || term->titlelen+len >= ESC_TITLE_SIZ) {
3023 term->esc = 0;
3024 term->title[term->titlelen] = '\0';
3025 fixWindowTitle(term);
3026 updateTabBar = 1;
3027 } else if (len > 0) {
3028 memcpy(term->title+term->titlelen, c, len);
3029 term->titlelen += len;
3030 term->title[term->titlelen] = '\0';
3032 } else if (term->esc & ESC_ALTCHARSET) {
3033 term->esc = 0;
3034 switch (ascii) {
3035 case '0': /* Line drawing crap */
3036 term->c.attr.mode |= ATTR_GFX;
3037 break;
3038 case 'B': /* Back to regular text */
3039 term->c.attr.mode &= ~ATTR_GFX;
3040 break;
3041 default:
3042 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
3043 term->c.attr.mode &= ~ATTR_GFX;
3044 break;
3046 } else if (term->esc & ESC_ALTG1) {
3047 term->esc = 0;
3048 switch (ascii) {
3049 case '0': /* Line drawing crap */
3050 term->c.attr.mode |= ATTR_GFX1;
3051 break;
3052 case 'B': /* Back to regular text */
3053 term->c.attr.mode &= ~ATTR_GFX1;
3054 break;
3055 default:
3056 fprintf(stderr, "esc unhandled charset: ESC ) %c\n", ascii);
3057 term->c.attr.mode &= ~ATTR_GFX1;
3058 break;
3060 } else if (term->esc & ESC_HASH) {
3061 term->esc = 0;
3062 switch (ascii) {
3063 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
3064 //tfillscreenwithE();
3065 break;
3067 } else if (term->esc & ESC_PERCENT) {
3068 term->esc = 0;
3069 } else {
3070 switch (ascii) {
3071 case '[': term->esc |= ESC_CSI; break;
3072 case ']': term->esc |= ESC_OSC; break;
3073 case '(': term->esc |= ESC_ALTCHARSET; break;
3074 case ')': term->esc |= ESC_ALTG1; break;
3075 case '#': term->esc |= ESC_HASH; break;
3076 case '%': term->esc |= ESC_PERCENT; break;
3077 case 'D': /* IND -- Linefeed */
3078 term->esc = 0;
3079 if (term->c.y == term->bot) tscrollup(term->top, 1); else tmoveto(term->c.x, term->c.y+1);
3080 break;
3081 case 'E': /* NEL -- Next line */
3082 term->esc = 0;
3083 tnewline(1); /* always go to first col */
3084 break;
3085 case 'M': /* RI -- Reverse linefeed */
3086 term->esc = 0;
3087 if (term->c.y == term->top) tscrolldown(term->top, 1); else tmoveto(term->c.x, term->c.y-1);
3088 break;
3089 case 'c': /* RIS -- Reset to inital state */
3090 term->esc = 0;
3091 treset();
3092 break;
3093 case '=': /* DECPAM -- Application keypad */
3094 DUMP_KEYPAD_SWITCH("=", "ON");
3095 term->esc = 0;
3096 term->mode |= MODE_APPKEYPAD;
3097 break;
3098 case '>': /* DECPNM -- Normal keypad */
3099 DUMP_KEYPAD_SWITCH(">", "OFF");
3100 term->esc = 0;
3101 term->mode &= ~MODE_APPKEYPAD;
3102 break;
3103 case '7': /* DECSC -- Save Cursor */
3104 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
3105 //TODO?
3106 term->esc = 0;
3107 tcursor(CURSOR_SAVE);
3108 break;
3109 case '8': /* DECRC -- Restore Cursor */
3110 //TODO?
3111 term->esc = 0;
3112 tcursor(CURSOR_LOAD);
3113 break;
3114 case 'Z': /* DEC private identification */
3115 term->esc = 0;
3116 ttywritestr("\x1b[?1;2c");
3117 break;
3118 default:
3119 term->esc = 0;
3120 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X ('%c')\n", (uchar)ascii, isprint(ascii)?ascii:'.');
3121 break;
3124 } else {
3125 //if (term->sel.bx != -1 && BETWEEN(term->c.y, term->sel.by, term->sel.ey)) term->sel.bx = -1;
3126 do {
3127 if (term->needConv && IS_GFX(term->c.attr.mode)) {
3128 ulong cc;
3130 utf8decode(c, &cc);
3131 if (cc < 32 || cc >= 127) break; //FIXME: nothing at all
3132 } else {
3133 if ((unsigned char)ascii < 32 || ascii == 127) break; // seems that this chars are empty too
3135 if (IS_SET(MODE_WRAP) && (term->c.state&CURSOR_WRAPNEXT)) tnewline(1); // always go to first col
3136 tsetchar(c);
3137 if (term->c.x+1 < term->col) tmoveto(term->c.x+1, term->c.y); else term->c.state |= CURSOR_WRAPNEXT;
3138 } while (0);
3143 static void tunshowhistory (void) {
3144 if (term != NULL && term->topline != 0) {
3145 term->topline = 0;
3146 term->wantRedraw = 1;
3147 term->lastDrawTime = 0;
3152 static void tsendfocusevent (int focused) {
3153 if (term != NULL && IS_SET(MODE_FOCUSEVT)) {
3154 ttywritestr("\x1b[");
3155 ttywrite(focused?"I":"O", 1);
3160 static void tcmdlinedirty (void) {
3161 if (term != NULL) {
3162 markDirty(term->row-term->topline-1, 2);
3163 term->wantRedraw = 1;
3168 static void tcmdlinefixofs (void) {
3169 int ofs, len;
3171 len = utf8strlen(term->cmdline);
3172 ofs = len-(term->col-1);
3173 if (ofs < 0) ofs = 0;
3174 for (term->cmdofs = 0; ofs > 0; --ofs) term->cmdofs += utf8size(term->cmdline+term->cmdofs);
3175 tcmdlinedirty();
3179 static void tcmdlinehide (void) {
3180 term->cmdMode = CMDMODE_NONE;
3181 tcmdlinedirty();
3185 // utf-8
3186 static void tcmdlinemsg (const char *msg) {
3187 if (msg != NULL) {
3188 int ofs = 0;
3190 term->cmdMode = CMDMODE_MESSAGE;
3191 term->cmdofs = 0;
3193 while (*msg) {
3194 int len = utf8size(msg);
3196 if (len < 1 || ofs+len >= sizeof(term->cmdline)-1) break;
3197 memcpy(term->cmdline+ofs, msg, len);
3198 ofs += len;
3199 msg += len;
3202 term->cmdline[ofs] = 0;
3203 tcmdlinedirty();
3208 static void tcmdlineinit (void) {
3209 term->cmdMode = CMDMODE_INPUT;
3210 term->cmdofs = 0;
3211 term->cmdline[0] = 0;
3212 term->cmdc[0] = 0;
3213 term->cmdcl = 0;
3214 tcmdlinefixofs();
3218 // utf-8
3219 static void tcmdaddchar (const char *s) {
3220 int len = utf8size(s);
3222 if (len > 0) {
3223 int slen = strlen(term->cmdline);
3225 if (slen+len < sizeof(term->cmdline)) {
3226 memcpy(term->cmdline+slen, s, len);
3227 term->cmdline[slen+len] = 0;
3228 tcmdlinefixofs();
3234 static void tcmdput (const char *s, int len) {
3235 while (len-- > 0) {
3236 int ok;
3238 term->cmdc[term->cmdcl++] = *s++;
3239 term->cmdc[term->cmdcl] = 0;
3241 if ((ok = isfullutf8(term->cmdc, term->cmdcl)) != 0 || term->cmdcl == UTF_SIZ) {
3242 if (ok) tcmdaddchar(term->cmdc);
3243 term->cmdcl = 0;
3249 ////////////////////////////////////////////////////////////////////////////////
3250 // tty resising
3251 static int tresize (int col, int row) {
3252 int mincol = MIN(col, term->col);
3253 int slide = term->c.y-row+1;
3254 Glyph g;
3256 if (col < 1 || row < 1) return 0;
3258 g.state = GLYPH_DIRTY;
3259 g.mode = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG;
3260 g.fg = term->deffg;
3261 g.bg = term->defbg;
3262 g.c[0] = ' ';
3263 g.c[1] = 0;
3265 if (slide > 0) {
3266 tsetscroll(0, term->row-1);
3267 for (; slide > 0; --slide) tscrollup(0, 1); // to fill history
3270 if (row < term->row) {
3271 /* free unneeded rows */
3272 for (int f = row; f < term->row; ++f) free(term->alt[f]);
3273 for (int f = term->linecount-(term->row-row); f < term->linecount; ++f) free(term->line[f]);
3274 term->linecount -= (term->row-row);
3275 /* resize to new height */
3276 term->alt = realloc(term->alt, row*sizeof(Line));
3277 term->line = realloc(term->line, term->linecount*sizeof(Line));
3278 } else if (row > term->row) {
3279 /* resize to new height */
3280 term->alt = realloc(term->alt, row*sizeof(Line));
3281 term->line = realloc(term->line, (row+term->maxhistory)*sizeof(Line));
3282 /* add more lines */
3283 for (int f = term->row; f < row; ++f) {
3284 term->alt[f] = calloc(col, sizeof(Glyph));
3285 for (int x = 0; x < col; ++x) term->alt[f][x] = g;
3287 for (int f = 0; f < row-term->row; ++f) {
3288 int y = term->linecount++;
3290 term->line[y] = calloc(col, sizeof(Glyph));
3291 for (int x = 0; x < col; ++x) term->line[y][x] = g;
3295 if (row != term->row) {
3296 term->dirty = realloc(term->dirty, row*sizeof(*term->dirty));
3299 /* resize each row to new width, zero-pad if needed */
3300 for (int f = 0; f < term->linecount; ++f) {
3301 term->line[f] = realloc(term->line[f], col*sizeof(Glyph));
3302 for (int x = mincol; x < col; ++x) term->line[f][x] = g;
3303 if (f < row) {
3304 markDirty(f, 2);
3305 term->alt[f] = realloc(term->alt[f], col*sizeof(Glyph));
3306 for (int x = mincol; x < col; ++x) term->alt[f][x] = g;
3309 /* update terminal size */
3310 term->topline = 0;
3311 term->col = col;
3312 term->row = row;
3313 /* make use of the LIMIT in tmoveto */
3314 tmoveto(term->c.x, term->c.y);
3315 /* reset scrolling region */
3316 tsetscroll(0, row-1);
3317 tfulldirt();
3318 return (slide > 0);
3322 static void xresize (int col, int row) {
3323 Pixmap newbuf;
3324 int oldw, oldh;
3326 if (term == NULL) return;
3327 oldw = term->picbufw;
3328 oldh = term->picbufh;
3329 term->picbufw = MAX(1, col*xw.cw);
3330 term->picbufh = MAX(1, row*xw.ch);
3331 newbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3332 XCopyArea(xw.dpy, term->picbuf, newbuf, dc.gc, 0, 0, term->picbufw, term->picbufh, 0, 0);
3333 XFreePixmap(xw.dpy, term->picbuf);
3334 XSetForeground(xw.dpy, dc.gc, getColor(term->defbg));
3335 if (term->picbufw > oldw) {
3336 XFillRectangle(xw.dpy, newbuf, dc.gc, oldw, 0, term->picbufw-oldw, MIN(term->picbufh, oldh));
3337 } else if (term->picbufw < oldw && xw.w > term->picbufw) {
3338 XClearArea(xw.dpy, xw.win, term->picbufw, 0, xw.w-term->picbufh, MIN(term->picbufh, oldh), False);
3340 if (term->picbufh > oldh) {
3341 XFillRectangle(xw.dpy, newbuf, dc.gc, 0, oldh, term->picbufw, term->picbufh-oldh);
3342 } else if (term->picbufh < oldh && xw.h > term->picbufh) {
3343 XClearArea(xw.dpy, xw.win, 0, term->picbufh, xw.w, xw.h-term->picbufh, False);
3345 term->picbuf = newbuf;
3346 tfulldirt();
3347 updateTabBar = 1;
3351 ////////////////////////////////////////////////////////////////////////////////
3352 // x11 drawing and utils
3354 static void xcreatebw (void) {
3355 if ((dc.bcol = calloc(MAX_COLOR+1, sizeof(dc.bcol[0]))) == NULL) die("out of memory");
3357 for (int f = 0; f <= MAX_COLOR; ++f) {
3358 XColor nclr;
3360 nclr = dc.ncol[f].pixel;
3361 XQueryColor(xw.dpy, xw.cmap, &nclr);
3362 fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", f, nclr.red, nclr.green, nclr.blue);
3368 static void xallocbwclr (int idx, XColor *color) {
3369 double lumi;
3371 XQueryColor(xw.dpy, xw.cmap, color);
3372 //fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", idx, color->red, color->green, color->blue);
3374 lumi = 0.3*((double)color->red/65535.0)+0.59*((double)color->green/65535.0)+0.11*((double)color->blue/65535.0);
3375 color->red = color->green = color->blue = (int)(lumi*65535.0);
3376 if (!XAllocColor(xw.dpy, xw.cmap, color)) {
3377 fprintf(stderr, "WARNING: could not allocate b/w color #%d\n", idx);
3378 return;
3380 dc.bcol[idx] = color->pixel;
3381 color->red = color->blue = 0;
3382 if (!XAllocColor(xw.dpy, xw.cmap, color)) {
3383 fprintf(stderr, "WARNING: could not allocate b/w color #%d\n", idx);
3384 return;
3386 dc.gcol[idx] = color->pixel;
3390 static void xallocnamedclr (int idx, const char *cname) {
3391 XColor color;
3393 if (!XAllocNamedColor(xw.dpy, xw.cmap, cname, &color, &color)) {
3394 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", idx, cname);
3395 return;
3397 dc.ncol[idx] = color.pixel;
3398 xallocbwclr(idx, &color);
3402 static void xloadcols (void) {
3403 int f, r, g, b;
3404 XColor color;
3405 ulong white = WhitePixel(xw.dpy, xw.scr);
3407 if ((dc.clrs[0] = dc.ncol = calloc(MAX_COLOR+1, sizeof(dc.ncol[0]))) == NULL) die("out of memory");
3408 if ((dc.clrs[1] = dc.bcol = calloc(MAX_COLOR+1, sizeof(dc.bcol[0]))) == NULL) die("out of memory");
3409 if ((dc.clrs[2] = dc.gcol = calloc(MAX_COLOR+1, sizeof(dc.gcol[0]))) == NULL) die("out of memory");
3411 for (f = 0; f <= MAX_COLOR; ++f) dc.ncol[f] = dc.bcol[f] = white;
3412 /* load colors [0-15] */
3413 for (f = 0; f <= 15; ++f) {
3414 const char *cname = opt_colornames[f]!=NULL?opt_colornames[f]:defcolornames[f];
3416 xallocnamedclr(f, cname);
3418 /* load colors [256-...] */
3419 for (f = 256; f <= MAX_COLOR; ++f) {
3420 const char *cname = opt_colornames[f];
3422 if (cname == NULL) {
3423 if (LEN(defextcolornames) <= f-256) continue;
3424 cname = defextcolornames[f-256];
3426 if (cname == NULL) continue;
3427 xallocnamedclr(f, cname);
3429 /* load colors [16-255] ; same colors as xterm */
3430 for (f = 16, r = 0; r < 6; ++r) {
3431 for (g = 0; g < 6; ++g) {
3432 for (b = 0; b < 6; ++b) {
3433 if (opt_colornames[f] != NULL) {
3434 xallocnamedclr(f, opt_colornames[f]);
3435 } else {
3436 color.red = r == 0 ? 0 : 0x3737+0x2828*r;
3437 color.green = g == 0 ? 0 : 0x3737+0x2828*g;
3438 color.blue = b == 0 ? 0 : 0x3737+0x2828*b;
3439 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3440 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3441 } else {
3442 dc.ncol[f] = color.pixel;
3443 xallocbwclr(f, &color);
3446 ++f;
3450 for (r = 0; r < 24; ++r, ++f) {
3451 if (opt_colornames[f] != NULL) {
3452 xallocnamedclr(f, opt_colornames[f]);
3453 } else {
3454 color.red = color.green = color.blue = 0x0808+0x0a0a*r;
3455 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3456 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3457 } else {
3458 dc.ncol[f] = color.pixel;
3459 xallocbwclr(f, &color);
3464 for (int f = 0; f < LEN(opt_colornames); ++f) if (opt_colornames[f]) free(opt_colornames[f]);
3468 static void xclear (int x1, int y1, int x2, int y2) {
3469 XSetForeground(xw.dpy, dc.gc, getColor(IS_SET(MODE_REVERSE) ? term->deffg : term->defbg));
3470 XFillRectangle(xw.dpy, term->picbuf, dc.gc, x1*xw.cw, y1*xw.ch, (x2-x1+1)*xw.cw, (y2-y1+1)*xw.ch);
3474 static void xhints (void) {
3475 XClassHint class = {opt_class, opt_title};
3476 XWMHints wm = {.flags = InputHint, .input = 1};
3477 XSizeHints size = {
3478 .flags = PSize | PResizeInc | PBaseSize,
3479 .height = xw.h,
3480 .width = xw.w,
3481 .height_inc = xw.ch,
3482 .width_inc = xw.cw,
3483 .base_height = xw.h/*xw.tabheight*/,
3484 .base_width = xw.w,
3486 //XSetWMNormalHints(xw.dpy, xw.win, &size);
3487 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, &size, &wm, &class);
3491 static XFontSet xinitfont (const char *fontstr) {
3492 XFontSet set;
3493 char *def, **missing;
3494 int n;
3496 missing = NULL;
3497 set = XCreateFontSet(xw.dpy, fontstr, &missing, &n, &def);
3498 if (missing) {
3499 while (n--) fprintf(stderr, "sterm: missing fontset: %s\n", missing[n]);
3500 XFreeStringList(missing);
3502 return set;
3506 static void xgetfontinfo (XFontSet set, int *ascent, int *descent, short *lbearing, short *rbearing, Font *fid) {
3507 XFontStruct **xfonts;
3508 char **font_names;
3509 int n;
3511 *ascent = *descent = *lbearing = *rbearing = 0;
3512 n = XFontsOfFontSet(set, &xfonts, &font_names);
3513 for (int f = 0; f < n; ++f) {
3514 if (f == 0) *fid = (*xfonts)->fid;
3515 *ascent = MAX(*ascent, (*xfonts)->ascent);
3516 *descent = MAX(*descent, (*xfonts)->descent);
3517 *lbearing = MAX(*lbearing, (*xfonts)->min_bounds.lbearing);
3518 *rbearing = MAX(*rbearing, (*xfonts)->max_bounds.rbearing);
3519 ++xfonts;
3524 static void initfonts (const char *fontstr, const char *bfontstr, const char *tabfont) {
3525 if ((dc.font[0].set = xinitfont(fontstr)) == NULL) {
3526 if ((dc.font[0].set = xinitfont(FONT)) == NULL) die("can't load font %s", fontstr);
3528 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);
3530 if ((dc.font[1].set = xinitfont(bfontstr)) == NULL) {
3531 if ((dc.font[1].set = xinitfont(FONTBOLD)) == NULL) die("can't load font %s", bfontstr);
3533 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);
3535 if ((dc.font[2].set = xinitfont(tabfont)) == NULL) {
3536 if ((dc.font[2].set = xinitfont(FONTTAB)) == NULL) die("can't load font %s", tabfont);
3538 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);
3542 static void xinit (void) {
3543 XSetWindowAttributes attrs;
3544 Window parent;
3545 XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
3547 if (!(xw.dpy = XOpenDisplay(NULL))) die("can't open display");
3549 XA_VT_SELECTION = XInternAtom(xw.dpy, "_STERM_SELECTION_", 0);
3550 XA_CLIPBOARD = XInternAtom(xw.dpy, "CLIPBOARD", 0);
3551 XA_UTF8 = XInternAtom(xw.dpy, "UTF8_STRING", 0);
3552 XA_NETWM_NAME = XInternAtom(xw.dpy, "_NET_WM_NAME", 0);
3553 XA_TARGETS = XInternAtom(xw.dpy, "TARGETS", 0);
3554 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
3556 xw.scr = XDefaultScreen(xw.dpy);
3557 /* font */
3558 initfonts(opt_fontnorm, opt_fontbold, opt_fonttab);
3559 /* XXX: Assuming same size for bold font */
3560 xw.cw = dc.font[0].rbearing-dc.font[0].lbearing;
3561 xw.ch = dc.font[0].ascent+dc.font[0].descent;
3562 xw.tch = dc.font[2].ascent+dc.font[2].descent;
3563 xw.tabheight = opt_disabletabs ? 0 : xw.tch+2;
3564 //fprintf(stderr, "tabh: %d\n", xw.tabheight);
3565 //xw.tabheight = 0;
3566 /* colors */
3567 xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
3568 xloadcols();
3569 /* window - default size */
3570 term->picbufh = term->row*xw.ch;
3571 term->picbufw = term->col*xw.cw;
3573 xw.h = term->picbufh+xw.tabheight;
3574 xw.w = term->picbufw;
3576 attrs.background_pixel = getColor(defaultBG);
3577 attrs.border_pixel = getColor(defaultBG);
3578 attrs.bit_gravity = NorthWestGravity;
3579 attrs.event_mask = FocusChangeMask | KeyPressMask
3580 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
3581 | /*ButtonMotionMask*/ PointerMotionMask | ButtonPressMask | ButtonReleaseMask
3582 | EnterWindowMask | LeaveWindowMask;
3583 attrs.colormap = xw.cmap;
3584 //fprintf(stderr, "oe: [%s]\n", opt_embed);
3585 parent = opt_embed ? strtol(opt_embed, NULL, 0) : XRootWindow(xw.dpy, xw.scr);
3586 xw.win = XCreateWindow(xw.dpy, parent, 0, 0,
3587 xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
3588 XDefaultVisual(xw.dpy, xw.scr),
3589 CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask
3590 | CWColormap,
3591 &attrs);
3592 xhints();
3593 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3594 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
3595 /* input methods */
3596 if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) die("XOpenIM() failed");
3597 xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL);
3598 /* gc */
3599 dc.gc = XCreateGC(xw.dpy, xw.win, 0, NULL);
3600 /* white cursor, black outline */
3601 xw.cursor = XCreateFontCursor(xw.dpy, XC_xterm);
3602 XDefineCursor(xw.dpy, xw.win, xw.cursor);
3603 XRecolorCursor(xw.dpy, xw.cursor,
3604 &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff},
3605 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
3606 fixWindowTitle(term);
3607 //XStoreName(xw.dpy, xw.win, opt_title);
3609 XSetForeground(xw.dpy, dc.gc, 0);
3610 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
3611 if (xw.tabheight > 0) XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
3613 XMapWindow(xw.dpy, xw.win);
3615 #if BLANKPTR_USE_GLYPH_CURSOR
3616 xw.blankPtr = XCreateGlyphCursor(xw.dpy, dc.font[0].fid, dc.font[0].fid, ' ', ' ', &blackcolor, &blackcolor);
3617 #else
3618 static const char cmbmp[1] = {0};
3619 Pixmap pm;
3621 pm = XCreateBitmapFromData(xw.dpy, xw.win, cmbmp, 1, 1);
3622 xw.blankPtr = XCreatePixmapCursor(xw.dpy, pm, pm, &blackcolor, &blackcolor, 0, 0);
3623 XFreePixmap(xw.dpy, pm);
3624 #endif
3626 XSync(xw.dpy, 0);
3630 static void xblankPointer (void) {
3631 if (!ptrBlanked && xw.blankPtr != None) {
3632 ptrBlanked = 1;
3633 XDefineCursor(xw.dpy, xw.win, xw.blankPtr);
3634 XFlush(xw.dpy);
3639 static void xunblankPointer (void) {
3640 if (ptrBlanked && xw.cursor != None) {
3641 ptrBlanked = 0;
3642 XDefineCursor(xw.dpy, xw.win, xw.cursor);
3643 XFlush(xw.dpy);
3644 ptrLastMove = mclock_ticks();
3649 static void xdraws (const char *s, const Glyph *base, int x, int y, int charlen, int bytelen) {
3650 int fg = base->fg, bg = base->bg, temp;
3651 int winx = x*xw.cw, winy = y*xw.ch+dc.font[0].ascent, width = charlen*xw.cw;
3652 XFontSet fontset = dc.font[0].set;
3653 int defF = base->mode&ATTR_DEFFG, defB = base->mode&ATTR_DEFBG;
3655 /* only switch default fg/bg if term is in RV mode */
3656 if (IS_SET(MODE_REVERSE)) {
3657 if (defF) fg = term->defbg;
3658 if (defB) bg = term->deffg;
3660 if (base->mode&ATTR_REVERSE) defF = defB = 0;
3661 if (base->mode & ATTR_BOLD) {
3662 if (defF && defB && defaultBoldFG >= 0) fg = defaultBoldFG;
3663 else if (fg < 8) fg += 8;
3664 fontset = dc.font[1].set;
3666 if (base->mode & ATTR_UNDERLINE && defaultUnderlineFG >= 0) {
3667 if (defF && defB) fg = defaultUnderlineFG;
3670 if (base->mode&ATTR_REVERSE) { temp = fg; fg = bg; bg = temp; }
3672 XSetBackground(xw.dpy, dc.gc, getColor(bg));
3673 XSetForeground(xw.dpy, dc.gc, getColor(fg));
3677 FILE *fo = fopen("zlog.log", "ab");
3678 fprintf(fo, "xdraws: x=%d; y=%d; charlen=%d; bytelen=%d\n", x, y, charlen, bytelen);
3679 fclose(fo);
3683 if (IS_GFX(base->mode)) {
3684 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
3685 } else if (!needConversion) {
3686 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
3687 } else {
3688 if (bytelen > 0) {
3689 //k8: dunno why, but Xutf8DrawImageString() ignores ascii (at least for terminus)
3690 const char *pos = s;
3691 int xpos = winx;
3693 while (pos < s+bytelen) {
3694 const char *e;
3695 int clen;
3697 if ((unsigned char)(pos[0]) < 128) {
3698 for (e = pos+1; e < s+bytelen && (unsigned char)(*e) < 128; ++e) ;
3699 clen = e-pos;
3700 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
3703 FILE *fo = fopen("zlog.log", "ab");
3704 fprintf(fo, " norm: clen=%d (%d); [", clen, (int)(e-pos));
3705 fwrite(pos, 1, e-pos, fo);
3706 fprintf(fo, "]\n");
3707 fclose(fo);
3710 } else {
3711 for (clen = 0, e = pos; e < s+bytelen && (unsigned char)(*e) >= 128; ++e) {
3712 if (((unsigned char)(e[0])&0xc0) == 0xc0) ++clen;
3714 Xutf8DrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
3717 FILE *fo = fopen("zlog.log", "ab");
3718 fprintf(fo, " utf8: clen=%d (%d); [", clen, (int)(e-pos));
3719 fwrite(pos, 1, e-pos, fo);
3720 fprintf(fo, "]\n");
3721 fclose(fo);
3725 xpos += xw.cw*clen;
3726 pos = e;
3731 if (base->mode & ATTR_UNDERLINE) {
3732 XDrawLine(xw.dpy, term->picbuf, dc.gc, winx, winy+1, winx+width-1, winy+1);
3737 /* copy buffer pixmap to screen pixmap */
3738 static void xcopy (int x, int y, int cols, int rows) {
3739 int src_x = x*xw.cw, src_y = y*xw.ch, src_w = cols*xw.cw, src_h = rows*xw.ch;
3740 int dst_x = src_x, dst_y = src_y;
3742 if (opt_tabposition == 1) { dst_y += xw.tabheight; }
3743 XCopyArea(xw.dpy, term->picbuf, xw.win, dc.gc, src_x, src_y, src_w, src_h, dst_x, dst_y);
3747 static void xdrawcursor (void) {
3748 Glyph g;
3749 int sl, scrx, scry, cmy;
3751 if (term == NULL) return;
3753 LIMIT(term->oldcx, 0, term->col-1);
3754 LIMIT(term->oldcy, 0, term->row-1);
3756 cmy = term->row-term->topline-1;
3758 if (term->cmdMode == CMDMODE_NONE || term->oldcy != cmy) {
3759 scrx = term->oldcx;
3760 scry = term->oldcy+term->topline;
3761 if (scry < term->row &&
3762 (term->oldcy != term->c.y || term->oldcx != term->c.x || (term->c.state&CURSOR_HIDE) || !(xw.state & WIN_FOCUSED))) {
3763 /* remove the old cursor */
3764 sl = utf8size(term->line[term->oldcy][scrx].c);
3765 g = term->line[term->oldcy][scrx];
3766 if (selected(scrx, term->c.y)) g.mode ^= ATTR_REVERSE;
3767 xdraws(g.c, &g, scrx, scry, 1, sl);
3768 //xclear(scrx, term->oldcy, scrx, term->oldcy);
3769 xcopy(scrx, scry, 1, 1);
3772 if (term->cmdMode != CMDMODE_NONE && term->oldcy == cmy) return;
3773 /* draw the new one */
3774 if (!(term->c.state&CURSOR_HIDE)) {
3775 scrx = term->c.x;
3776 scry = term->c.y+term->topline;
3777 if (scry < term->row) {
3778 if (!(xw.state & WIN_FOCUSED)) {
3779 if (defaultCursorInactiveBG < 0) {
3780 XSetForeground(xw.dpy, dc.gc, getColor(defaultCursorBG));
3781 XDrawRectangle(xw.dpy, term->picbuf, dc.gc, scrx*xw.cw, scry*xw.ch, xw.cw-1, xw.ch-1);
3782 goto done;
3784 g.bg = defaultCursorInactiveBG;
3785 g.fg = defaultCursorInactiveFG;
3786 } else {
3787 g.fg = defaultCursorFG;
3788 g.bg = defaultCursorBG;
3790 memcpy(g.c, term->line[term->c.y][scrx].c, UTF_SIZ);
3791 g.state = 0;
3792 g.mode = 0;
3793 if (IS_SET(MODE_REVERSE)) g.mode |= ATTR_REVERSE;
3794 sl = utf8size(g.c);
3795 xdraws(g.c, &g, scrx, scry, 1, sl);
3796 term->oldcx = scrx;
3797 term->oldcy = term->c.y;
3799 done:
3800 xcopy(scrx, scry, 1, 1);
3805 static void xdrawTabBar (void) {
3806 if (xw.tabheight > 0 && updateTabBar) {
3807 static int tableft = 0;
3808 int tabstart;
3809 int tabw = xw.w/opt_tabcount;
3810 XFontSet fontset = dc.font[2].set;
3812 XSetForeground(xw.dpy, dc.gc, getColor(normalTabBG));
3813 XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
3815 if (termidx < tableft) tableft = termidx;
3816 else if (termidx > tableft+opt_tabcount-1) tableft = termidx-opt_tabcount+1;
3817 if (tableft < 0) tableft = 0;
3819 tabstart = tableft;
3820 for (int f = tabstart; f < tabstart+opt_tabcount; ++f) {
3821 int x = (f-tabstart)*tabw;
3822 const char *title;
3823 char *tit;
3825 if (f >= term_count) {
3826 XSetForeground(xw.dpy, dc.gc, getColor(normalTabBG));
3827 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, xw.w, xw.tabheight);
3829 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
3830 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
3831 break;
3833 title = term_array[f]->title;
3834 if (!title[0]) title = opt_title;
3835 tit = SPrintf("[%d]%s", f, title);
3836 title = tit;
3838 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
3839 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, tabw, xw.tabheight);
3841 XSetBackground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
3842 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabFG : normalTabFG));
3844 if (needConversion) {
3845 int xx = x+2;
3847 while (*title && xx < x+tabw) {
3848 const char *e = title;
3849 XRectangle r;
3851 memset(&r, 0, sizeof(r));
3853 if ((unsigned char)(*e) > 127) {
3854 while (*e && (unsigned char)(*e) > 127) ++e;
3855 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
3856 Xutf8TextExtents(fontset, title, e-title, &r, NULL);
3857 } else {
3858 while (*e && (unsigned char)(*e) <= 127) ++e;
3859 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
3860 XmbTextExtents(fontset, title, e-title, &r, NULL);
3862 title = e;
3863 xx += r.width-r.x;
3866 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
3867 } else {
3868 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
3870 free(tit);
3872 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
3873 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x+tabw-2, 0, 2, xw.tabheight);
3875 if (f > tabstart) {
3876 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
3877 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
3881 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
3882 //XDrawRectangle(xw.dpy, xw.pictab, dc.gc, -1, 0, xw.w+1, xw.tabheight+1);
3883 if (opt_tabposition == 0) {
3884 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, 0);
3885 } else {
3886 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, xw.tabheight-1, xw.w, xw.tabheight-1);
3889 if (opt_tabposition == 0) {
3890 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, xw.h-xw.tabheight);
3891 } else {
3892 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, 0);
3894 updateTabBar = 0;
3898 static void drawcmdline (int scry) {
3899 Glyph base;
3900 int cpos = term->cmdofs, bc = 0, x, sx;
3901 int back = (term->cmdMode == CMDMODE_INPUT ? 21 : 124);
3903 base.mode = 0;
3904 base.fg = 255;
3905 base.bg = back;
3906 base.state = 0;
3907 for (sx = x = 0; x < term->col && term->cmdline[cpos]; ++x) {
3908 int l = utf8size(term->cmdline+cpos);
3910 if (bc+l > DRAW_BUF_SIZ) {
3911 xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
3912 bc = 0;
3913 sx = x;
3915 memcpy(term->drawbuf+bc, term->cmdline+cpos, l);
3916 cpos += l;
3917 bc += l;
3919 if (bc > 0) xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
3921 if (x < term->col && term->cmdMode == CMDMODE_INPUT) {
3922 base.fg = back;
3923 base.bg = 255;
3924 xdraws(" ", &base, x, scry, 1, 1);
3925 ++x;
3928 if (x < term->col) {
3929 base.fg = 255;
3930 base.bg = back;
3931 memset(term->drawbuf, ' ', DRAW_BUF_SIZ);
3932 while (x < term->col) {
3933 sx = x;
3934 x += DRAW_BUF_SIZ;
3935 if (x > term->col) x = term->col;
3936 xdraws(term->drawbuf, &base, sx, scry, x-sx, x-sx);
3940 xcopy(0, scry, term->col, 1);
3944 static void drawline (int x1, int x2, int scry, int lineno) {
3945 int ic, ib, ox, sl;
3946 int stx, ex;
3947 Glyph base, new;
3949 //dlogf("drawline: x1=%d; x2=%d; scry=%d; row:%d; lineno=%d\n", x1, x2, scry, term->row, lineno);
3950 if (scry < 0 || scry >= term->row) return;
3952 if (scry == term->row-1 && term->cmdMode != CMDMODE_NONE) { drawcmdline(scry); return; }
3954 if (lineno < 0 || lineno >= term->linecount) {
3955 xclear(0, scry, term->col-1, scry);
3956 xcopy(0, scry, term->col, 1);
3957 } else {
3958 if (lineno < term->row && term->topline == 0) {
3959 //if (term->topline != 0) term->dirty[lineno] = 2;
3960 if (!term->dirty[lineno]) return;
3961 // fix 'dirty' flag for line
3962 if (term->dirty[lineno]&0x02) {
3963 // mark full line as dirty
3964 for (int x = 0; x < term->col; ++x) term->line[lineno][x].state |= GLYPH_DIRTY;
3966 // correct 'dirty' flag
3967 term->dirty[lineno] = 0;
3968 if (x1 > 0) for (int x = 0; x < x1; ++x) if (term->line[lineno][x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
3969 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; }
3971 // find dirty region
3972 for (stx = x1; stx < x2; ++stx) if (term->line[lineno][stx].state&GLYPH_DIRTY) break;
3973 for (ex = x2; ex > stx; --ex) if (term->line[lineno][ex-1].state&GLYPH_DIRTY) break;
3974 if (stx >= x2 || ex <= stx) return; // nothing to do
3975 //dlogf(" region: (%d,%d)\n", stx, ex);
3976 } else {
3977 //if (lineno < term->row) term->dirty[lineno] = 0;
3978 stx = 0;
3979 ex = term->col;
3982 base = term->line[lineno][stx];
3983 ic = ib = 0;
3984 ox = stx;
3985 //xclear(stx, scry, ex-1, scry); //k8: actually, we don't need this for good monospace fonts, so...
3986 for (int x = stx; x < ex; ++x) {
3987 new = term->line[lineno][x];
3988 term->line[lineno][x].state &= ~GLYPH_DIRTY; //!
3989 if (term->sel.bx != -1 && new.c[0]) {
3990 if ((lineno < term->row && selected(x, lineno)) || (lineno >= term->row && selected(x, 0-(lineno-term->row+1)))) new.mode ^= ATTR_REVERSE;
3992 if (ib > 0 && (ATTRCMP(base, new) || ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
3993 // flush draw buffer
3994 xdraws(term->drawbuf, &base, ox, scry, ic, ib);
3995 ic = ib = 0;
3997 if (ib == 0) { ox = x; base = new; }
3998 sl = utf8size(new.c);
3999 memcpy(term->drawbuf+ib, new.c, sl);
4000 ib += sl;
4001 ++ic;
4003 if (ib > 0) xdraws(term->drawbuf, &base, ox, scry, ic, ib);
4004 //xcopy(0, scry, term->col, 1);
4005 if (term->c.y == lineno && term->c.x >= stx && term->c.x < ex) xdrawcursor();
4006 xcopy(stx, scry, ex-stx, 1);
4011 static void drawregion (int x1, int y1, int x2, int y2, int forced) {
4012 if (!forced && (xw.state&WIN_VISIBLE) == 0) {
4013 //dlogf("invisible");
4014 lastDrawTime = term->lastDrawTime = 1;
4015 term->wantRedraw = 1;
4016 return;
4019 if (term->topline < term->row) {
4020 for (int y = y1; y < y2; ++y) drawline(x1, x2, y+term->topline, y);
4022 if (term->topline > 0) {
4023 int scry = MIN(term->topline, term->row), y = term->row;
4025 if (term->topline >= term->row) y += term->topline-term->row;
4026 while (--scry >= 0) {
4027 drawline(0, term->col, scry, y);
4028 ++y;
4031 xdrawcursor();
4032 xdrawTabBar();
4033 //XFlush(xw.dpy);
4034 lastDrawTime = term->lastDrawTime = mclock_ticks();
4035 term->wantRedraw = 0;
4039 static void draw (int forced) {
4040 if (term != NULL) {
4041 //fprintf(stderr, "draw(%d) (%d)\n", forced, mclock_ticks());
4042 drawregion(0, 0, term->col, term->row, forced);
4047 static void expose (XEvent *ev) {
4048 XExposeEvent *e = &ev->xexpose;
4050 if (xw.state&WIN_REDRAW) {
4051 if (!e->count && term != NULL) {
4052 xw.state &= ~WIN_REDRAW;
4053 xcopy(0, 0, term->col, term->row);
4055 } else if (term != NULL) {
4056 //XCopyArea(xw.dpy, term->picbuf, xw.win, dc.gc, e->x, e->y, e->width, e->height, e->x, e->y+(opt_tabposition==1?xw.height:0)));
4057 xcopy(0, 0, term->col, term->row);
4059 xdrawTabBar();
4060 //XFlush(xw.dpy);
4064 static void visibility (XEvent *ev) {
4065 XVisibilityEvent *e = &ev->xvisibility;
4067 if (e->state == VisibilityFullyObscured) xw.state &= ~WIN_VISIBLE;
4068 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 */
4072 static void unmap (XEvent *ev) {
4073 xw.state &= ~WIN_VISIBLE;
4077 static void xseturgency (int add) {
4078 XWMHints *h = XGetWMHints(xw.dpy, xw.win);
4080 h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
4081 XSetWMHints(xw.dpy, xw.win, h);
4082 XFree(h);
4086 static void focus (XEvent *ev) {
4087 if (ev->type == FocusIn) {
4088 xw.state |= WIN_FOCUSED;
4089 xseturgency(0);
4090 tsendfocusevent(1);
4091 } else {
4092 xw.state &= ~WIN_FOCUSED;
4093 tsendfocusevent(0);
4095 //draw(1);
4096 xdrawcursor();
4097 xdrawTabBar();
4098 xcopy(0, 0, term->col, term->row);
4102 ////////////////////////////////////////////////////////////////////////////////
4103 // keyboard mapping
4104 static const char *kmap (KeySym k, uint state) {
4105 const char *res = NULL;
4107 state &= ~Mod2Mask; // numlock
4108 for (int f = 0; f < keymap_used; ++f) {
4109 uint mask = keymap[f].mask;
4111 if (keymap[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) {
4112 //fprintf(stderr, "kp: %d\n", IS_SET(MODE_APPKEYPAD));
4113 if (!IS_SET(MODE_APPKEYPAD)) {
4114 if (!keymap[f].kp) {
4115 //fprintf(stderr, "nokp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4116 return keymap[f].str; // non-keypad hit
4118 continue;
4120 //fprintf(stderr, "kp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4121 if (keymap[f].kp) return keymap[f].str; // keypad hit
4122 res = keymap[f].str; // kp mode, but non-kp mapping found
4125 return res;
4129 static const char *kbind (KeySym k, uint state) {
4130 state &= ~Mod2Mask; // numlock
4131 for (int f = 0; f < keybinds_used; ++f) {
4132 uint mask = keybinds[f].mask;
4134 if (keybinds[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) return keybinds[f].str;
4136 return NULL;
4140 static KeySym do_keytrans (KeySym ks) {
4141 for (int f = 0; f < keytrans_used; ++f) if (keytrans[f].src == ks) return keytrans[f].dst;
4142 return ks;
4146 static void kpress (XEvent *ev) {
4147 XKeyEvent *e = &ev->xkey;
4148 KeySym ksym = NoSymbol;
4149 const char *kstr;
4150 int len;
4151 Status status;
4152 char buf[32];
4154 if (term == NULL) return;
4156 if (!ptrBlanked && opt_ptrblank > 0) xblankPointer();
4158 //if (len < 1) len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
4159 if ((len = Xutf8LookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status)) > 0) buf[len] = 0;
4160 // leave only known mods
4161 e->state &= (Mod1Mask | Mod4Mask | ControlMask | ShiftMask);
4162 #ifdef DUMP_KEYSYMS
4164 const char *ksname = XKeysymToString(ksym);
4166 fprintf(stderr, "utf(%d):[%s] (%s) 0x%08x\n", len, len>=0?buf:"<shit>", ksname, (unsigned int)e->state);
4168 #endif
4169 if ((kstr = kbind(ksym, e->state)) != NULL) {
4170 // keybind found
4171 executeCommands(kstr);
4172 return;
4175 if (term->cmdMode != CMDMODE_NONE) {
4176 int mode = term->cmdMode;
4178 switch (do_keytrans(ksym)) {
4179 case XK_Return:
4180 tcmdlinehide();
4181 if (mode == CMDMODE_INPUT) executeCommands(term->cmdline);
4182 break;
4183 case XK_BackSpace:
4184 if (mode == CMDMODE_INPUT) utf8choplast(term->cmdline);
4185 tcmdlinefixofs();
4186 break;
4187 case XK_Escape:
4188 tcmdlinehide();
4189 break;
4190 default:
4191 if (mode == CMDMODE_INPUT) {
4192 if (len > 0 && (unsigned char)buf[0] >= 32) tcmdput(buf, len);
4194 break;
4196 return;
4199 if ((kstr = kmap(do_keytrans(ksym), e->state)) != NULL) {
4200 if (kstr[0]) {
4201 tunshowhistory();
4202 ttywritestr(kstr);
4204 } else {
4205 int meta = (e->state&Mod1Mask);
4207 int shift = (e->state&ShiftMask);
4208 int ctrl = (e->state&ControlMask);
4210 switch (ksym) {
4211 case XK_Return:
4212 tunshowhistory();
4213 if (meta) {
4214 ttywritestr("\x1b\x0a");
4215 } else {
4216 if (IS_SET(MODE_CRLF)) ttywritestr("\r\n"); else ttywritestr("\r");
4218 break;
4219 default:
4220 if (len > 0) {
4221 tunshowhistory();
4222 if (meta && len == 1) ttywritestr("\x1b");
4223 ttywrite(buf, len);
4225 break;
4231 ////////////////////////////////////////////////////////////////////////////////
4232 // xembed?
4233 static void cmessage (XEvent *e) {
4234 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
4235 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
4236 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
4237 xw.state |= WIN_FOCUSED;
4238 xseturgency(0);
4239 tsendfocusevent(1);
4240 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
4241 xw.state &= ~WIN_FOCUSED;
4242 tsendfocusevent(0);
4244 xdrawcursor();
4245 xdrawTabBar();
4246 xcopy(0, 0, term->col, term->row);
4251 ////////////////////////////////////////////////////////////////////////////////
4252 static void resize (XEvent *e) {
4253 int col, row;
4254 Term *ot = term;
4256 //if (e->xconfigure.width == 65535 || e->xconfigure.width == -1) e->xconfigure.width = xw.w;
4257 if (e->xconfigure.height == 65535 || e->xconfigure.height == -1) e->xconfigure.height = xw.h;
4258 //if ((short int)e->xconfigure.height < xw.ch) return;
4260 if (e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) return;
4261 xw.w = e->xconfigure.width;
4262 xw.h = e->xconfigure.height;
4263 col = xw.w/xw.cw;
4264 row = (xw.h-xw.tabheight)/xw.ch;
4265 //fprintf(stderr, "neww=%d; newh=%d; ch=%d; th=%d; col=%d; row=%d; ocol=%d; orow=%d\n", xw.w, xw.h, xw.ch, xw.tabheight, col, row, term->col, term->row);
4266 if (col == term->col && row == term->row) return;
4267 for (int f = 0; f < term_count; ++f) {
4268 term = term_array[f];
4269 if (tresize(col, row) && ot == term) draw(1);
4270 ttyresize();
4271 xresize(col, row);
4273 term = ot;
4274 XFreePixmap(xw.dpy, xw.pictab);
4275 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
4276 updateTabBar = 1;
4280 static inline int last_draw_too_old (void) {
4281 int tt = mclock_ticks();
4283 if (term != NULL) {
4284 if (term->dead || !term->wantRedraw) return 0;
4285 if (tt-term->lastDrawTime >= opt_drawtimeout) return 1;
4286 return 0;
4288 return (tt-lastDrawTime >= 500);
4292 ////////////////////////////////////////////////////////////////////////////////
4293 // main loop
4294 static void (*handler[LASTEvent])(XEvent *) = {
4295 [KeyPress] = kpress,
4296 [ClientMessage] = cmessage,
4297 [ConfigureNotify] = resize,
4298 [VisibilityNotify] = visibility,
4299 [UnmapNotify] = unmap,
4300 [Expose] = expose,
4301 [FocusIn] = focus,
4302 [FocusOut] = focus,
4303 [MotionNotify] = bmotion,
4304 [ButtonPress] = bpress,
4305 [ButtonRelease] = brelease,
4306 [SelectionNotify] = selnotify,
4307 [SelectionRequest] = selrequest,
4308 [SelectionClear] = selclear,
4312 static void run (void) {
4313 //int stuff_to_print = 0;
4314 int xfd = XConnectionNumber(xw.dpy);
4316 ptrLastMove = mclock_ticks();
4317 while (term_count > 0) {
4318 XEvent ev;
4319 fd_set rfd, wfd;
4320 struct timeval timeout;
4321 int maxfd = xfd;
4322 Term *ot;
4324 FD_ZERO(&rfd);
4325 FD_ZERO(&wfd);
4326 FD_SET(xfd, &rfd);
4327 //FD_SET(term->cmdfd, &rfd);
4328 // have something to write?
4329 for (int f = 0; f < term_count; ++f) {
4330 Term *t = term_array[f];
4332 if (!t->dead && term->cmdfd >= 0 && t->pid != 0) {
4333 if (t->cmdfd > maxfd) maxfd = t->cmdfd;
4334 FD_SET(t->cmdfd, &rfd);
4335 if (t->wrbufpos < t->wrbufused) FD_SET(t->cmdfd, &wfd);
4339 timeout.tv_sec = 0;
4340 timeout.tv_usec = (opt_drawtimeout+2)*1000;
4341 if (select(maxfd+1, &rfd, &wfd, NULL, &timeout) < 0) {
4342 if (errno == EINTR) continue;
4343 die("select failed: %s", SERRNO);
4346 ot = term;
4347 for (int f = 0; f < term_count; ++f) {
4348 Term *t = term_array[f];
4350 if (!t->dead && term->cmdfd >= 0 && term->pid != 0) {
4351 term = t;
4352 if (FD_ISSET(t->cmdfd, &wfd)) ttyflushwrbuf();
4353 if (FD_ISSET(t->cmdfd, &rfd)) ttyread(); //t->wantRedraw = 1;
4354 term = ot;
4358 termcleanup();
4359 if (term_count == 0) exit(exitcode);
4361 if (updateTabBar || last_draw_too_old()) draw(0);
4363 if (XPending(xw.dpy)) {
4364 while (XPending(xw.dpy)) {
4365 XNextEvent(xw.dpy, &ev);
4366 switch (ev.type) {
4367 //case VisibilityNotify:
4368 //case UnmapNotify:
4369 //case FocusIn:
4370 //case FocusOut:
4371 case MotionNotify:
4372 case ButtonPress:
4373 case ButtonRelease:
4374 xunblankPointer();
4375 ptrLastMove = mclock_ticks();
4376 break;
4377 default: ;
4379 if (XFilterEvent(&ev, xw.win)) continue;
4380 if (handler[ev.type]) (handler[ev.type])(&ev);
4384 if (!ptrBlanked && opt_ptrblank > 0 && mclock_ticks()-ptrLastMove >= opt_ptrblank) {
4385 xblankPointer();
4391 ////////////////////////////////////////////////////////////////////////////////
4392 typedef const char * (*IniHandlerFn) (const char *optname, const char *fmt, char *argstr, void *udata);
4395 typedef struct {
4396 const char *name;
4397 const char *fmt;
4398 void *udata;
4399 IniHandlerFn fn;
4400 } IniCommand;
4402 static const char *inifnGenericOneArg (const char *optname, const char *fmt, char *argstr, void *udata) {
4403 return iniParseArguments(argstr, fmt, udata);
4407 static const char *inifnGenericOneStr (const char *optname, const char *fmt, char *argstr, void *udata) {
4408 char *s = NULL;
4409 const char *err = iniParseArguments(argstr, fmt, &s);
4411 if (err != NULL) return err;
4412 if ((s = strdup(s)) == NULL) return "out of memory";
4413 if (udata) {
4414 char **ustr = (char **)udata;
4416 if (*ustr) free(*ustr);
4417 *ustr = s;
4419 return NULL;
4423 static const IniCommand iniCommands[] = {
4424 {"term", "s!-", &opt_term, inifnGenericOneStr},
4425 {"class", "s!-", &opt_class, inifnGenericOneStr},
4426 {"title", "s!-", &opt_title, inifnGenericOneStr},
4427 {"fontnorm", "s!-", &opt_fontnorm, inifnGenericOneStr},
4428 {"fontbold", "s!-", &opt_fontbold, inifnGenericOneStr},
4429 {"fonttab", "s!-", &opt_fonttab, inifnGenericOneStr},
4430 {"shell", "s!-", &opt_shell, inifnGenericOneStr},
4431 {"doubleclick_timeout", "i{0,10000}", &opt_doubleclick_timeout, inifnGenericOneArg},
4432 {"tripleclick_timeout", "i{0,10000}", &opt_tripleclick_timeout, inifnGenericOneArg},
4433 {"tabsize", "i{1,256}", &opt_tabsize, inifnGenericOneArg},
4434 {"defaultfg", "i{0,511}", &defaultFG, inifnGenericOneArg},
4435 {"defaultbg", "i{0,511}", &defaultBG, inifnGenericOneArg},
4436 {"defaultcursorfg", "i{0,511}", &defaultCursorFG, inifnGenericOneArg},
4437 {"defaultcursorbg", "i{0,511}", &defaultCursorBG, inifnGenericOneArg},
4438 {"defaultinactivecursorfg", "i{0,511}", &defaultCursorInactiveFG, inifnGenericOneArg},
4439 {"defaultinactivecursorbg", "i{-1,511}", &defaultCursorInactiveBG, inifnGenericOneArg},
4440 {"defaultboldfg", "i{-1,511}", &defaultBoldFG, inifnGenericOneArg},
4441 {"defaultunderlinefg", "i{-1,511}", &defaultUnderlineFG, inifnGenericOneArg},
4442 {"normaltabfg", "i{0,511}", &normalTabFG, inifnGenericOneArg},
4443 {"normaltabbg", "i{0,511}", &normalTabBG, inifnGenericOneArg},
4444 {"activetabfg", "i{0,511}", &activeTabFG, inifnGenericOneArg},
4445 {"activetabbg", "i{0,511}", &activeTabBG, inifnGenericOneArg},
4446 {"maxhistory", "i{0,65535}", &opt_maxhistory, inifnGenericOneArg},
4447 {"ptrblank", "i{0,65535}", &opt_ptrblank, inifnGenericOneArg},
4448 {"tabcount", "i{1,128}", &opt_tabcount, inifnGenericOneArg},
4449 {"tabposition", "i{0,1}", &opt_tabposition, inifnGenericOneArg},
4450 {"draw_timeout", "i{5,30000}", &opt_drawtimeout, inifnGenericOneArg},
4451 {"audiblebell", "b", &opt_audiblebell, inifnGenericOneArg},
4452 {NULL, NULL, NULL, NULL}
4456 #define MISC_CMD_NONE ((const char *)-1)
4459 // NULL: command processed; MISC_CMD_NONE: unknown command; !NULL: error
4460 static const char *processMiscCmds (const char *optname, char *argstr) {
4461 const char *err = NULL;
4463 if (strcasecmp(optname, "unimap") == 0) {
4464 int uni, ch;
4465 char *alt = NULL;
4467 //unimap 0x2592 0x61 alt
4468 if ((err = iniParseArguments(argstr, "i{0,65535}i{0,255}|s!-", &uni, &ch, &alt)) != NULL) return err;
4469 if (alt != NULL && strcasecmp(alt, "alt") != 0) return "invalid unimap";
4470 if (unimap == NULL) {
4471 if ((unimap = calloc(65536, sizeof(unimap[0]))) == NULL) return "out of memory";
4473 if (alt != NULL && ch == 0) alt = NULL;
4474 if (alt != NULL && ch < 96) return "invalid unimap";
4475 unimap[uni] = ch;
4476 if (alt != NULL) unimap[uni] |= 0x8000;
4477 return NULL;
4480 if (strcasecmp(optname, "keytrans_reset") == 0) {
4481 if ((err = iniParseArguments(argstr, "")) != NULL) return err;
4482 keytrans_reset();
4483 return NULL;
4485 if (strcasecmp(optname, "keytrans") == 0) {
4486 char *src = NULL, *dst = NULL;
4488 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
4489 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
4490 keytrans_reset();
4491 return NULL;
4494 if ((err = iniParseArguments(argstr, "s!-s!-", &src, &dst)) != NULL) return err;
4495 keytrans_add(src, dst);
4496 return NULL;
4499 if (strcasecmp(optname, "keybind_reset") == 0) {
4500 if ((err = iniParseArguments(argstr, "")) != NULL) return err;
4501 keybinds_reset();
4502 return NULL;
4504 if (strcasecmp(optname, "keybind") == 0) {
4505 char *key = NULL, *act = NULL;
4506 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
4507 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
4508 keybinds_reset();
4509 return NULL;
4512 if ((err = iniParseArguments(argstr, "s!-R!", &key, &act)) != NULL) return err;
4513 keybind_add(key, act);
4514 return NULL;
4517 if (strcasecmp(optname, "keymap_reset") == 0) {
4518 if ((err = iniParseArguments(argstr, "")) != NULL) return err;
4519 keymap_reset();
4520 return NULL;
4522 if (strcasecmp(optname, "keymap") == 0) {
4523 char *key = NULL, *str = NULL;
4525 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
4526 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
4527 keymap_reset();
4528 return NULL;
4531 if ((err = iniParseArguments(argstr, "s!-s!-", &key, &str)) != NULL) return err;
4532 keymap_add(key, str);
4533 return NULL;
4536 return MISC_CMD_NONE;
4540 #define INI_LINE_SIZE (32768)
4542 // <0: file not found
4543 // >0: file loading error
4544 // 0: ok
4545 static int loadConfig (const char *fname) {
4546 int inifelse = 0; // 0: not; 1: doing true; 2: doing false; -1: waiting else/endif; -2: waiting endif
4547 FILE *fi = fopen(fname, "r");
4548 const char *err = NULL;
4549 char *line;
4550 int lineno = 0;
4552 if (fi == NULL) return -1;
4553 if ((line = malloc(INI_LINE_SIZE)) == NULL) { err = "out of memory"; goto quit; }
4555 while (fgets(line, INI_LINE_SIZE-1, fi) != NULL) {
4556 char *optname, *argstr;
4557 int goodoption = 0;
4559 ++lineno;
4560 line[INI_LINE_SIZE-1] = 0;
4561 // get option name
4562 for (optname = line; *optname && isspace(*optname); ++optname) ;
4563 if (!optname[0] || optname[0] == '#') continue; // comment
4564 if (!isalnum(optname[0])) { err = "invalid option name"; goto quit; }
4565 argstr = optname;
4566 while (*argstr) {
4567 if (!argstr[0] || isspace(argstr[0])) break;
4568 if (!isalnum(argstr[0]) && argstr[0] != '_' && argstr[0] != '.') { err = "invalid option name"; goto quit; }
4569 *argstr = tolower(*argstr);
4570 ++argstr;
4572 if (*argstr) *argstr++ = 0;
4574 if (strcasecmp(optname, "ifterm") == 0) {
4575 char *val;
4577 if (inifelse != 0) { err = "nested ifs are not allowed"; goto quit; }
4578 inifelse = -1;
4579 if ((err = iniParseArguments(argstr, "s", &val)) != NULL) goto quit;
4580 if (strcasecmp(opt_term, val) == 0) inifelse = 1;
4581 continue;
4583 if (strcasecmp(optname, "else") == 0) {
4584 switch (inifelse) {
4585 case -1: inifelse = 2; break;
4586 case 2: case -2: case 0: err = "else without if"; goto quit;
4587 case 1: inifelse = -2; break;
4589 continue;
4591 if (strcasecmp(optname, "endif") == 0) {
4592 switch (inifelse) {
4593 case -1: case -2: case 1: case 2: inifelse = 0; break;
4594 case 0: err = "endif without if"; goto quit;
4596 continue;
4599 if (inifelse < 0) {
4600 //trimstr(argstr);
4601 //fprintf(stderr, "skip: [%s]\n", argstr);
4602 continue;
4604 if (opt_term_locked && strcasecmp(optname, "term") == 0) continue; // termname given in command line
4605 // ok, we have option name in `optname` and arguments in `argstr`
4606 if (strncmp(optname, "color.", 6) == 0) {
4607 int n = 0;
4608 char *s = NULL;
4610 optname += 6;
4611 if (!optname[0]) { err = "invalid color option"; goto quit; }
4612 while (*optname) {
4613 if (!isdigit(*optname)) { err = "invalid color option"; goto quit; }
4614 n = (n*10)+(optname[0]-'0');
4615 ++optname;
4617 if (n < 0 || n > 511) { err = "invalid color index"; goto quit; }
4619 if ((err = iniParseArguments(argstr, "s!-", &s)) != NULL) goto quit;
4620 if ((s = strdup(s)) == NULL) { err = "out of memory"; goto quit; }
4621 if (opt_colornames[n] != NULL) free(opt_colornames[n]);
4622 opt_colornames[n] = s;
4623 continue;
4626 if ((err = processMiscCmds(optname, argstr)) != MISC_CMD_NONE) {
4627 if (err != NULL) goto quit;
4628 continue;
4629 } else {
4630 err = NULL;
4633 for (int f = 0; iniCommands[f].name != NULL; ++f) {
4634 if (strcmp(iniCommands[f].name, optname) == 0) {
4635 if ((err = iniCommands[f].fn(optname, iniCommands[f].fmt, argstr, iniCommands[f].udata)) != NULL) goto quit;
4636 goodoption = 1;
4637 break;
4640 if (!goodoption) {
4641 fprintf(stderr, "ini error at line %d: unknown option '%s'!\n", lineno, optname);
4644 quit:
4645 if (line != NULL) free(line);
4646 fclose(fi);
4647 if (err == NULL && inifelse != 0) err = "if without endif";
4648 if (err != NULL) die("ini error at line %d: %s", lineno, err);
4649 return 0;
4653 static void initDefaultOptions (void) {
4654 opt_title = strdup("sterm");
4655 opt_class = strdup("sterm");
4656 opt_term = strdup(TNAME);
4657 opt_fontnorm = strdup(FONT);
4658 opt_fontbold = strdup(FONTBOLD);
4659 opt_fonttab = strdup(FONTTAB);
4660 opt_shell = strdup(SHELL);
4662 memset(opt_colornames, 0, sizeof(opt_colornames));
4663 for (int f = 0; f < LEN(defcolornames); ++f) opt_colornames[f] = strdup(defcolornames[f]);
4664 for (int f = 0; f < LEN(defextcolornames); ++f) opt_colornames[f+256] = strdup(defextcolornames[f]);
4666 keytrans_add("KP_Home", "Home");
4667 keytrans_add("KP_Left", "Left");
4668 keytrans_add("KP_Up", "Up");
4669 keytrans_add("KP_Right", "Right");
4670 keytrans_add("KP_Down", "Down");
4671 keytrans_add("KP_Prior", "Prior");
4672 keytrans_add("KP_Next", "Next");
4673 keytrans_add("KP_End", "End");
4674 keytrans_add("KP_Begin", "Begin");
4675 keytrans_add("KP_Insert", "Insert");
4676 keytrans_add("KP_Delete", "Delete");
4678 keybind_add("shift+Insert", "PastePrimary");
4679 keybind_add("alt+Insert", "PasteSecondary");
4681 keymap_add("BackSpace", "\177");
4682 keymap_add("Insert", "\x1b[2~");
4683 keymap_add("Delete", "\x1b[3~");
4684 keymap_add("Home", "\x1b[1~");
4685 keymap_add("End", "\x1b[4~");
4686 keymap_add("Prior", "\x1b[5~");
4687 keymap_add("Next", "\x1b[6~");
4688 keymap_add("F1", "\x1bOP");
4689 keymap_add("F2", "\x1bOQ");
4690 keymap_add("F3", "\x1bOR");
4691 keymap_add("F4", "\x1bOS");
4692 keymap_add("F5", "\x1b[15~");
4693 keymap_add("F6", "\x1b[17~");
4694 keymap_add("F7", "\x1b[18~");
4695 keymap_add("F8", "\x1b[19~");
4696 keymap_add("F9", "\x1b[20~");
4697 keymap_add("F10", "\x1b[21~");
4698 keymap_add("Up", "\x1bOA");
4699 keymap_add("Down", "\x1bOB");
4700 keymap_add("Right", "\x1bOC");
4701 keymap_add("Left", "\x1bOD");
4702 keymap_add("kpad+Up", "\x1bOA");
4703 keymap_add("kpad+Down", "\x1bOB");
4704 keymap_add("kpad+Right", "\x1bOC");
4705 keymap_add("kpad+Left", "\x1bOD");
4709 ////////////////////////////////////////////////////////////////////////////////
4710 static Term *oldTerm;
4711 static int oldTermIdx;
4712 static Term *newTerm;
4713 static int newTermIdx;
4714 static int newTermSwitch;
4717 typedef void (*CmdHandlerFn) (const char *cmdname, char *argstr);
4719 typedef struct {
4720 const char *name;
4721 CmdHandlerFn fn;
4722 } Command;
4725 static void cmdPastePrimary (const char *cmdname, char *argstr) {
4726 selpaste(XA_PRIMARY);
4730 static void cmdPasteSecondary (const char *cmdname, char *argstr) {
4731 selpaste(XA_SECONDARY);
4735 static void cmdPasteClipboard (const char *cmdname, char *argstr) {
4736 selpaste(XA_CLIPBOARD);
4740 static void cmdExec (const char *cmdname, char *argstr) {
4741 if (term->execcmd != NULL) free(term->execcmd);
4742 if (argstr[0]) {
4743 term->execcmd = strdup(argstr);
4744 } else {
4745 term->execcmd = NULL;
4750 static int parseTabArgs (char *argstr, int *noswitch, int nowrap, int idx) {
4751 for (;;) {
4752 char *arg;
4754 while (*argstr && isspace(*argstr)) ++argstr;
4755 if (!argstr[0]) break;
4756 if (iniParseArguments(argstr, "s-R-", &arg, &argstr) != NULL) break;
4758 if (strcasecmp(arg, "noswitch") == 0) *noswitch = 1;
4759 else if (strcasecmp(arg, "switch") == 0) *noswitch = 0;
4760 else if (strcasecmp(arg, "nowrap") == 0) nowrap = 1;
4761 else if (strcasecmp(arg, "wrap") == 0) nowrap = 0;
4762 else if (strcasecmp(arg, "first") == 0) idx = 0;
4763 else if (strcasecmp(arg, "last") == 0) idx = term_count-1;
4764 else if (strcasecmp(arg, "prev") == 0) idx = -1;
4765 else if (strcasecmp(arg, "next") == 0) idx = -2;
4766 else {
4767 long int n = -1;
4768 char *eptr;
4770 n = strtol(arg, &eptr, 0);
4771 if (!eptr[0] && n >= 0 && n < term_count) idx = n;
4774 switch (idx) {
4775 case -1: // prev
4776 if ((idx = termidx-1) < 0) idx = nowrap ? 0 : term_count-1;
4777 break;
4778 case -2: // next
4779 if ((idx = termidx+1) >= term_count) idx = nowrap ? term_count-1 : 0;
4780 break;
4782 return idx;
4786 static void flushNewTerm (void) {
4787 if (newTerm != NULL) {
4788 if (newTermSwitch && term != NULL) term->lastActiveTime = mclock_ticks();
4789 term = newTerm;
4790 termidx = newTermIdx;
4791 tinitialize(term_array[0]->col, term_array[0]->row);
4793 term->picbufh = term->row*xw.ch;
4794 term->picbufw = term->col*xw.cw;
4795 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
4796 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
4798 if (ttynew(term) != 0) {
4799 term = oldTerm;
4800 termidx = oldTermIdx;
4801 termfree(newTermIdx);
4802 } else {
4803 selinit();
4804 ttyresize();
4805 if (newTermSwitch) {
4806 term = NULL;
4807 termidx = 0;
4808 switchToTerm(newTermIdx, 1);
4809 oldTerm = term;
4810 oldTermIdx = termidx;
4811 } else {
4812 term = oldTerm;
4813 termidx = oldTermIdx;
4816 newTerm = NULL;
4821 static void cmdNewTab (const char *cmdname, char *argstr) {
4822 int noswitch = 0, idx;
4824 if (opt_disabletabs) return;
4825 flushNewTerm();
4826 if ((newTerm = termalloc()) == NULL) return;
4827 /*idx =*/ parseTabArgs(argstr, &noswitch, 0, termidx);
4828 idx = term_count-1;
4829 if (!noswitch) {
4830 if (term != NULL) term->lastActiveTime = mclock_ticks();
4831 oldTermIdx = idx;
4833 newTermIdx = termidx = idx;
4834 term = newTerm;
4835 newTermSwitch = !noswitch;
4839 static void cmdCloseTab (const char *cmdname, char *argstr) {
4840 flushNewTerm();
4841 if (!term->dead) kill(term->pid, SIGTERM);
4845 static void cmdKillTab (const char *cmdname, char *argstr) {
4846 flushNewTerm();
4847 if (!term->dead) kill(term->pid, SIGKILL);
4851 static void cmdSwitchToTab (const char *cmdname, char *argstr) {
4852 int noswitch = 0, idx;
4854 flushNewTerm();
4855 idx = parseTabArgs(argstr, &noswitch, 0, -666);
4856 if (idx >= 0) {
4857 switchToTerm(idx, 1);
4858 oldTerm = term;
4859 oldTermIdx = termidx;
4864 static void cmdMoveTabTo (const char *cmdname, char *argstr) {
4865 int noswitch = 0, idx;
4867 flushNewTerm();
4868 idx = parseTabArgs(argstr, &noswitch, 0, termidx);
4869 if (idx != termidx && idx >= 0 && idx < term_count) {
4870 Term *t = term_array[termidx];
4872 // remove current term
4873 for (int f = termidx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
4874 // insert term
4875 for (int f = term_count-2; f >= idx; --f) term_array[f+1] = term_array[f];
4876 term_array[idx] = t;
4877 termidx = idx;
4878 oldTerm = t;
4879 oldTermIdx = idx;
4880 updateTabBar = 1;
4885 static void cmdDefaultFG (const char *cmdname, char *argstr) {
4886 char *s = NULL;
4887 int c;
4889 if (iniParseArguments(argstr, "i{0,511}|s-", &c, &s) == NULL) {
4890 if (s != NULL && tolower(s[0]) == 'g') defaultFG = c; else term->deffg = c;
4895 static void cmdDefaultBG (const char *cmdname, char *argstr) {
4896 char *s = NULL;
4897 int c;
4899 if (iniParseArguments(argstr, "i{0,511}|s-", &c) == NULL) {
4900 if (s != NULL && tolower(s[0]) == 'g') defaultBG = c; else term->defbg = c;
4905 static void scrollHistory (int delta) {
4906 if (term->maxhistory < 1) return; // no history
4907 term->topline += delta;
4908 if (term->topline > term->maxhistory-term->row) term->topline = term->maxhistory-term->row;
4909 if (term->topline < 0) term->topline = 0;
4910 tfulldirt();
4911 draw(1);
4915 static void cmdScrollHistoryLineUp (const char *cmdname, char *argstr) {
4916 scrollHistory(1);
4920 static void cmdScrollHistoryPageUp (const char *cmdname, char *argstr) {
4921 scrollHistory(term->row);
4925 static void cmdScrollHistoryLineDown (const char *cmdname, char *argstr) {
4926 scrollHistory(-1);
4930 static void cmdScrollHistoryPageDown (const char *cmdname, char *argstr) {
4931 scrollHistory(-term->row);
4935 static void cmdScrollHistoryTop (const char *cmdname, char *argstr) {
4936 scrollHistory(term->linecount);
4940 static void cmdScrollHistoryBottom (const char *cmdname, char *argstr) {
4941 scrollHistory(-term->linecount);
4945 static void cmdUTF8Locale (const char *cmdname, char *argstr) {
4946 int b;
4948 if (iniParseArguments(argstr, "b", &b) == NULL) {
4949 if (!needConversion) b = 1;
4950 if (term != NULL) term->needConv = !b;
4951 //fprintf(stderr, "needConv: %d (%d)\n", term->needConv, needConversion);
4956 static void cmdAudibleBell (const char *cmdname, char *argstr) {
4957 if (term == NULL) return;
4959 int b = -1;
4960 int toggle = 0, *iptr = &term->audiblebell;
4962 while (argstr[0]) {
4963 char *s = NULL;
4965 if (iniParseArguments(argstr, "R-", &argstr) != NULL) break;
4966 if (!argstr[0]) break;
4968 if (iniParseArguments(argstr, "s!-R-", &s, &argstr) != NULL) break;
4969 if (strcasecmp(s, "toggle") == 0) toggle = 1;
4970 else if (tolower(s[0]) == 'g') iptr = &opt_audiblebell;
4971 else {
4972 if (iniParseArguments(s, "b", &b) != NULL) return;
4976 if (b == -1) {
4977 tcmdlinemsg(*iptr ? "AudibleBell: 1" : "AudibleBell: 0");
4978 } else {
4979 if (toggle) *iptr = !(*iptr); else *iptr = b;
4984 static void cmdCommandMode (const char *cmdname, char *argstr) {
4985 if (term != NULL) {
4986 if (term->cmdMode == CMDMODE_NONE) tcmdlineinit();
4991 // [show|hide]
4992 static void cmdCursor (const char *cmdname, char *argstr) {
4993 if (term != NULL) {
4994 char *s;
4996 if (iniParseArguments(argstr, "s!-", &s) != NULL) {
4997 tcmdlinemsg((term->c.state&CURSOR_HIDE) ? "cursor is hidden" : "cursor is visible");
4998 } else {
4999 if (strcasecmp(s, "show") == 0) term->c.state &= ~CURSOR_HIDE;
5000 else if (strcasecmp(s, "hide") == 0) term->c.state |= CURSOR_HIDE;
5001 term->wantRedraw = 1;
5007 static void cmdResetAttrs (const char *cmdname, char *argstr) {
5008 if (term != NULL) {
5009 term->c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
5010 term->c.attr.mode |= ATTR_DEFFG | ATTR_DEFBG;
5011 term->c.attr.fg = term->deffg;
5012 term->c.attr.bg = term->defbg;
5017 static void cmdResetCharset (const char *cmdname, char *argstr) {
5018 if (term != NULL) {
5019 term->c.attr.mode &= ~(ATTR_GFX|ATTR_GFX1);
5024 // [norm|alt]
5025 static void cmdScreen (const char *cmdname, char *argstr) {
5026 if (term != NULL) {
5027 char *s;
5029 if (iniParseArguments(argstr, "s!-", &s) != NULL) {
5030 tcmdlinemsg(IS_SET(MODE_ALTSCREEN) ? "screen: alt" : "screen: norm");
5031 } else {
5032 if (strcasecmp(s, "norm") == 0) {
5033 if (IS_SET(MODE_ALTSCREEN)) tswapscreen();
5034 } else if (strcasecmp(s, "alt") == 0) {
5035 if (!IS_SET(MODE_ALTSCREEN)) tswapscreen();
5042 // [on|off]
5043 static void cmdMouseReports (const char *cmdname, char *argstr) {
5044 if (term != NULL) {
5045 int b;
5047 if (iniParseArguments(argstr, "b", &b) != NULL) {
5048 tcmdlinemsg(IS_SET(MODE_MOUSE) ? "mouse reports are on" : "mouse reports are off");
5049 } else {
5050 if (b) term->mode |= MODE_MOUSEBTN; else term->mode &= ~MODE_MOUSEBTN;
5056 static void cmdMonochrome (const char *cmdname, char *argstr) {
5057 int b = -1, global = 0;
5059 if (term == NULL) return;
5061 while (argstr[0]) {
5062 char *s = NULL;
5064 if (iniParseArguments(argstr, "R-", &argstr) != NULL) break;
5065 if (!argstr[0]) break;
5067 if (iniParseArguments(argstr, "s!-R-", &s, &argstr) != NULL) break;
5068 if (tolower(s[0]) == 'g') global = 1;
5069 else {
5070 if (iniParseArguments(s, "i{0,2}", &b) != NULL) return;
5074 if (b == -1) {
5075 char buf[32];
5077 b = (global ? globalBW : term->blackandwhite);
5078 sprintf(buf, "Monochrome: %d", b);
5079 tcmdlinemsg(buf);
5080 } else {
5081 if (global) globalBW = b; else term->blackandwhite = b;
5083 tfulldirt();
5087 static const Command commandList[] = {
5088 {"PastePrimary", cmdPastePrimary},
5089 {"PasteSecondary", cmdPasteSecondary},
5090 {"PasteClipboard", cmdPasteClipboard},
5091 {"exec", cmdExec},
5092 {"NewTab", cmdNewTab}, // 'noswitch' 'next' 'prev' 'first' 'last'
5093 {"CloseTab", cmdCloseTab},
5094 {"KillTab", cmdKillTab},
5095 {"SwitchToTab", cmdSwitchToTab}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5096 {"MoveTabTo", cmdMoveTabTo}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5097 {"defaultfg", cmdDefaultFG},
5098 {"defaultbg", cmdDefaultBG},
5099 {"ScrollHistoryLineUp", cmdScrollHistoryLineUp},
5100 {"ScrollHistoryPageUp", cmdScrollHistoryPageUp},
5101 {"ScrollHistoryLineDown", cmdScrollHistoryLineDown},
5102 {"ScrollHistoryPageDown", cmdScrollHistoryPageDown},
5103 {"ScrollHistoryTop", cmdScrollHistoryTop},
5104 {"ScrollHistoryBottom", cmdScrollHistoryBottom},
5105 {"UTF8Locale", cmdUTF8Locale}, // 'on', 'off'
5106 {"AudibleBell", cmdAudibleBell},
5107 {"CommandMode", cmdCommandMode},
5108 {"Cursor", cmdCursor},
5109 {"ResetAttrs", cmdResetAttrs},
5110 {"ResetCharset", cmdResetCharset},
5111 {"Screen", cmdScreen},
5112 {"MouseReports", cmdMouseReports},
5113 {"Monochrome", cmdMonochrome},
5114 {"Mono", cmdMonochrome},
5116 {"term", cmdTermName},
5117 {"title", cmdWinTitle},
5118 {"tabsize", cmdTabSize},
5119 {"defaultcursorfg", cmdDefaultCursorFG},
5120 {"defaultcursorbg", cmdDefaultCursorBG},
5121 {"defaultinactivecursorfg", cmdDefaultInactiveCursorFG},
5122 {"defaultinactivecursorbg", cmdDefaultInactiveCursorBG},
5123 {"defaultboldfg", cmdDefaultBoldFG},
5124 {"defaultunderlinefg", cmdDefaultUnderlineFG},
5126 {NULL, NULL}
5130 // !0: NewTab command
5131 static int executeCommand (const char *str, int slen) {
5132 const char *e;
5133 char *cmdname;
5134 int cmdfound = 0;
5136 if (str == NULL) return 0;
5137 if (slen < 0) slen = strlen(str);
5139 for (int f = 0; f < slen; ++f) if (!str[f]) { slen = f; break; }
5141 while (slen > 0 && isspace(*str)) { ++str; --slen; }
5142 if (slen < 1 || !str[0]) return 0;
5144 for (e = str; slen > 0 && !isspace(*e); ++e, --slen) ;
5146 if (e-str > 127) return 0;
5147 cmdname = alloca(e-str+8);
5148 if (cmdname == NULL) return 0;
5149 memcpy(cmdname, str, e-str);
5150 cmdname[e-str] = 0;
5151 if (opt_disabletabs && strcasecmp(cmdname, "NewTab") == 0) return 1;
5153 while (slen > 0 && isspace(*e)) { ++e; --slen; }
5154 //FIXME: ugly copypaste!
5156 for (int f = 0; commandList[f].name != NULL; ++f) {
5157 if (strcasecmp(commandList[f].name, cmdname) == 0) {
5158 char *left = calloc(slen+2, 1);
5160 if (left != NULL) {
5161 if (slen > 0) memcpy(left, e, slen);
5162 //fprintf(stderr, "command: [%s]; args: [%s]\n", cmdname, left);
5163 commandList[f].fn(cmdname, left);
5164 free(left);
5166 cmdfound = 1;
5167 break;
5171 if (!cmdfound) {
5172 char *left = calloc(slen+2, 1);
5174 if (left != NULL) {
5175 if (slen > 0) memcpy(left, e, slen);
5176 processMiscCmds(cmdname, left);
5177 free(left);
5181 return 0;
5185 static void executeCommands (const char *str) {
5186 oldTerm = term;
5187 oldTermIdx = termidx;
5188 newTerm = NULL;
5189 newTermSwitch = 0;
5190 if (str == NULL) return;
5191 while (*str) {
5192 const char *ce;
5193 char qch;
5195 while (*str && isspace(*str)) ++str;
5196 if (!*str) break;
5197 if (*str == ';') { ++str; continue; }
5199 ce = str;
5200 qch = ' ';
5201 while (*ce) {
5202 if (*ce == ';' && qch == ' ') break;
5203 if (qch != ' ' && *ce == qch) { qch = ' '; ++ce; continue; }
5204 if (*ce == '"' || *ce == '\'') {
5205 if (qch == ' ') qch = *ce;
5206 ++ce;
5207 continue;
5209 if (*ce++ == '\\' && *ce) ++ce;
5212 if (executeCommand(str, ce-str)) break;
5213 if (*ce) str = ce+1; else break;
5215 flushNewTerm();
5216 switchToTerm(oldTermIdx, 1);
5220 ////////////////////////////////////////////////////////////////////////////////
5221 int main (int argc, char *argv[]) {
5222 char *configfile = NULL;
5224 //dbgLogInit();
5226 for (int f = 1; f < argc; f++) {
5227 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
5228 if (strcmp(argv[f], "-into") == 0) { ++f; continue; }
5229 if (strcmp(argv[f], "-embed") == 0) { ++f; continue; }
5230 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
5231 case 'e': f = argc+1; break;
5232 case 't':
5233 case 'c':
5234 case 'w':
5235 case 'b':
5236 ++f;
5237 break;
5238 case 'T':
5239 if (++f < argc) {
5240 free(opt_term);
5241 opt_term = strdup(argv[f]);
5242 opt_term_locked = 1;
5244 break;
5245 case 'C':
5246 if (++f < argc) {
5247 if (configfile != NULL) free(configfile);
5248 configfile = strdup(argv[f]);
5250 break;
5251 case 'l':
5252 if (++f < argc) cliLocale = argv[f];
5253 break;
5254 case 'S': // single-tab mode
5255 opt_disabletabs = 1;
5256 break;
5257 case 'v':
5258 case 'h':
5259 default:
5260 fprintf(stderr, "%s", USAGE);
5261 exit(EXIT_FAILURE);
5265 initDefaultOptions();
5266 if (configfile == NULL) {
5267 const char *home = getenv("HOME");
5269 if (home != NULL) {
5270 configfile = SPrintf("%s/.sterm.rc", home);
5271 if (loadConfig(configfile) == 0) goto cfgdone;
5272 free(configfile); configfile = NULL;
5274 configfile = SPrintf("%s/.config/sterm.rc", home);
5275 if (loadConfig(configfile) == 0) goto cfgdone;
5276 free(configfile); configfile = NULL;
5279 configfile = SPrintf("/etc/sterm.rc");
5280 if (loadConfig(configfile) == 0) goto cfgdone;
5281 free(configfile); configfile = NULL;
5283 configfile = SPrintf("/etc/sterm/sterm.rc");
5284 if (loadConfig(configfile) == 0) goto cfgdone;
5285 free(configfile); configfile = NULL;
5287 configfile = SPrintf("./.sterm.rc");
5288 if (loadConfig(configfile) == 0) goto cfgdone;
5289 free(configfile); configfile = NULL;
5290 // no config
5291 } else {
5292 if (loadConfig(configfile) < 0) die("config file '%s' not found!", configfile);
5294 cfgdone:
5295 if (configfile != NULL) free(configfile); configfile = NULL;
5297 for (int f = 1; f < argc; f++) {
5298 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
5299 if (strcmp(argv[f], "-into") == 0 || strcmp(argv[f], "-embed") == 0) {
5300 if (opt_embed) free(opt_embed);
5301 opt_embed = strdup(argv[f]);
5302 continue;
5304 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
5305 case 't':
5306 if (++f < argc) {
5307 free(opt_title);
5308 opt_title = strdup(argv[f]);
5310 break;
5311 case 'c':
5312 if (++f < argc) {
5313 free(opt_class);
5314 opt_class = strdup(argv[f]);
5316 break;
5317 case 'w':
5318 if (++f < argc) {
5319 if (opt_embed) free(opt_embed);
5320 opt_embed = strdup(argv[f]);
5322 break;
5323 case 'e':
5324 /* eat every remaining arguments */
5325 if (++f < argc) opt_cmd = &argv[f];
5326 f = argc+1;
5327 case 'T':
5328 case 'C':
5329 case 'l':
5330 ++f;
5331 break;
5332 case 'S':
5333 break;
5334 case 'v':
5335 case 'h':
5336 default:
5337 fprintf(stderr, "%s", USAGE);
5338 exit(EXIT_FAILURE);
5342 setenv("TERM", opt_term, 1);
5343 mclock_init();
5344 setlocale(LC_ALL, "");
5345 initLCConversion();
5346 updateTabBar = 1;
5347 termidx = 0;
5348 term = termalloc();
5349 if (term->execcmd != NULL) { free(term->execcmd); term->execcmd = NULL; }
5350 tinitialize(80, 25);
5351 if (ttynew(term) != 0) die("can't run process");
5352 opt_cmd = NULL;
5353 xinit();
5354 selinit();
5355 run();
5356 return 0;