fixed bug in word selection in history; slightly better selection processing
[k8sterm.git] / src / sterm.c
blobd4a7c166da7172e899c9f56e647b5726e31137d4
1 /* See LICENSE for licence details. */
2 #define VERSION "0.3.0.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] [-v] [-e command...]\n"
71 ////////////////////////////////////////////////////////////////////////////////
72 #define FONT "-*-liberation mono-medium-*-*-*-17-*-*-*-*-*-koi8-u"
73 #define BOLDFONT "-*-liberation mono-medium-*-*-*-17-*-*-*-*-*-koi8-u"
74 #define FONTTAB "-*-helvetica-*-*-*-*-12-*-*-*-*-*-*-*"
76 #define FONT "-*-terminus-bold-*-*-*-20-*-*-*-*-*-koi8-u"
77 #define BOLDFONT "-*-terminus-bold-*-*-*-20-*-*-*-*-*-koi8-u"
81 /* Default shell to use if SHELL is not set in the env */
82 #define SHELL "/bin/sh"
85 /* Terminal colors (16 first used in escape sequence) */
86 static const char *defcolornames[] = {
87 #if 1
88 /* 8 normal colors */
89 "black",
90 "red3",
91 "green3",
92 "yellow3",
93 "blue2",
94 "magenta3",
95 "cyan3",
96 "gray90",
97 /* 8 bright colors */
98 "gray50",
99 "red",
100 "green",
101 "yellow",
102 "#5c5cff",
103 "magenta",
104 "cyan",
105 "white",
106 #else
107 /* 8 normal colors */
108 "#000000",
109 "#b21818",
110 "#18b218",
111 "#b26818",
112 "#1818b2",
113 "#b218b2",
114 "#18b2b2",
115 "#b2b2b2",
116 /* 8 bright colors */
117 "#686868",
118 "#ff5454",
119 "#54ff54",
120 "#ffff54",
121 "#5454ff",
122 "#ff54ff",
123 "#54ffff",
124 "#ffffff",
125 #endif
129 /* more colors can be added after 255 to use with DefaultXX */
130 static const char *defextcolornames[] = {
131 "#cccccc", /* 256 */
132 "#333333", /* 257 */
133 /* root terminal fg and bg */
134 "#809a70", /* 258 */
135 "#002000", /* 259 */
136 /* bold and underline */
137 "#00afaf", /* 260 */
138 "#00af00", /* 261 */
142 /* Default colors (colorname index) foreground, background, cursor, unfocused cursor */
143 #define DEFAULT_FG (7)
144 #define DEFAULT_BG (0)
145 #define DEFAULT_CS (256)
146 #define DEFAULT_UCS (257)
148 #define TNAME "xterm"
150 /* double-click timeout (in milliseconds) between clicks for selection */
151 #define DOUBLECLICK_TIMEOUT (300)
152 #define TRIPLECLICK_TIMEOUT (2*DOUBLECLICK_TIMEOUT)
153 //#define SELECT_TIMEOUT 20 /* 20 ms */
154 #define DRAW_TIMEOUT 18 /* 18 ms */
156 #define TAB (8)
159 ////////////////////////////////////////////////////////////////////////////////
160 #define MAX_COLOR (511)
162 /* XEMBED messages */
163 #define XEMBED_FOCUS_IN (4)
164 #define XEMBED_FOCUS_OUT (5)
167 /* Arbitrary sizes */
168 #define ESC_TITLE_SIZ (256)
169 #define ESC_BUF_SIZ (256)
170 #define ESC_ARG_SIZ (16)
171 #define DRAW_BUF_SIZ (2048)
172 #define UTF_SIZ (4)
173 #define OBUFSIZ (256)
174 #define WBUFSIZ (256)
177 /* masks for key translation */
178 #define XK_NO_MOD (UINT_MAX)
179 #define XK_ANY_MOD (0)
182 /* misc utility macros */
183 //#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000+(t1.tv_usec-t2.tv_usec)/1000)
184 #define SERRNO strerror(errno)
185 #define MIN(a, b) ((a) < (b) ? (a) : (b))
186 #define MAX(a, b) ((a) < (b) ? (b) : (a))
187 #define LEN(a) (sizeof(a)/sizeof(a[0]))
188 #define DEFAULT(a, b) (a) = ((a) ? (a) : (b))
189 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
190 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
191 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
192 #define IS_SET(flag) (term->mode&(flag))
193 #define X2COL(x) ((x)/xw.cw)
194 #define Y2ROW(y) ((y-(opt_tabposition==1 ? xw.tabheight : 0))/xw.ch-(term!=NULL ? term->topline : 0))
195 #define IS_GFX(mode) (((mode)&(ATTR_GFX|ATTR_G1)) == ATTR_GFX || ((mode)&(ATTR_GFX1|ATTR_G1)) == (ATTR_GFX1|ATTR_G1))
198 ////////////////////////////////////////////////////////////////////////////////
199 enum glyph_attribute {
200 ATTR_NULL = 0x00,
201 ATTR_REVERSE = 0x01,
202 ATTR_UNDERLINE = 0x02,
203 ATTR_BOLD = 0x04,
204 ATTR_GFX = 0x08,
205 ATTR_DEFFG = 0x10,
206 ATTR_DEFBG = 0x20,
207 ATTR_G1 = 0x40,
208 ATTR_GFX1 = 0x80,
211 enum cursor_movement {
212 CURSOR_UP,
213 CURSOR_DOWN,
214 CURSOR_LEFT,
215 CURSOR_RIGHT,
216 CURSOR_SAVE,
217 CURSOR_LOAD
220 enum cursor_state {
221 CURSOR_DEFAULT = 0,
222 CURSOR_HIDE = 1,
223 CURSOR_WRAPNEXT = 2
226 enum glyph_state {
227 GLYPH_SET = 0x01, /* for selection only */
228 GLYPH_DIRTY = 0x02,
232 enum term_mode {
233 MODE_WRAP = 0x01,
234 MODE_INSERT = 0x02,
235 MODE_APPKEYPAD = 0x04,
236 MODE_ALTSCREEN = 0x08,
237 MODE_CRLF = 0x10,
238 MODE_MOUSEBTN = 0x20,
239 MODE_MOUSEMOTION = 0x40,
240 MODE_MOUSE = 0x20|0x40,
241 MODE_REVERSE = 0x80,
244 enum escape_state {
245 ESC_START = 0x01,
246 ESC_CSI = 0x02,
247 ESC_OSC = 0x04,
248 ESC_TITLE = 0x08,
249 ESC_ALTCHARSET = 0x10,
250 ESC_HASH = 0x20,
251 ESC_PERCENT = 0x40,
252 ESC_ALTG1 = 0x80,
255 enum window_state {
256 WIN_VISIBLE = 0x01,
257 WIN_REDRAW = 0x02,
258 WIN_FOCUSED = 0x04,
261 /* bit macro */
262 #undef B0
263 enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 };
266 ////////////////////////////////////////////////////////////////////////////////
267 typedef unsigned char uchar;
268 typedef unsigned int uint;
269 typedef unsigned long ulong;
270 typedef unsigned short ushort;
273 typedef struct __attribute__((packed)) {
274 char c[UTF_SIZ]; /* character code */
275 uchar mode; /* attribute flags */
276 ushort fg; /* foreground */
277 ushort bg; /* background */
278 uchar state; /* state flags */
279 } Glyph;
282 typedef Glyph *Line;
284 typedef struct {
285 Glyph attr; /* current char attributes */
286 int x;
287 int y;
288 char state;
289 } TCursor;
292 /* CSI Escape sequence structs */
293 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
294 typedef struct {
295 char buf[ESC_BUF_SIZ]; /* raw string */
296 int len; /* raw string length */
297 char priv;
298 int arg[ESC_ARG_SIZ];
299 int narg; /* nb of args */
300 char mode;
301 } CSIEscape;
304 /* Purely graphic info */
305 typedef struct {
306 Display *dpy;
307 Colormap cmap;
308 Window win;
309 Cursor cursor;
310 Cursor blankPtr;
311 Atom xembed;
312 XIM xim;
313 XIC xic;
314 int scr;
315 int w; /* window width */
316 int h; /* window height */
317 int bufw; /* pixmap width */
318 int bufh; /* pixmap height */
319 int ch; /* char height */
320 int cw; /* char width */
321 char state; /* focus, redraw, visible */
323 int tch; /* tab text char height */
324 Pixmap pictab;
325 int tabheight;
326 //struct timeval lastdraw;
327 } XWindow;
330 /* TODO: use better name for vars... */
331 typedef struct {
332 int mode;
333 int bx, by;
334 int ex, ey;
335 struct { int x, y; } b, e;
336 char *clip;
337 Atom xtarget;
338 int tclick1;
339 int tclick2;
340 } Selection;
343 /* Drawing Context */
344 typedef struct {
345 ulong *col; //[LEN(colorname) < 256 ? 256 : LEN(colorname)];
346 GC gc;
347 struct {
348 int ascent;
349 int descent;
350 short lbearing;
351 short rbearing;
352 XFontSet set;
353 Font fid;
354 } font[3];
355 } DC;
358 /* Internal representation of the screen */
359 typedef struct {
360 int cmdfd;
361 int dead;
362 int needConv; /* 0: utf-8 locale */
364 int row; /* nb row */
365 int col; /* nb col */
366 int topline; /* top line for drawing (0: no history; 1: show one history line; etc) */
367 int linecount; /* full, with history */
368 int maxhistory;/* max history lines; 0: none; <0: infinite */
369 Line *line; /* screen */
370 Line *alt; /* alternate screen */
371 char *dirty; /* dirtyness of lines */
372 TCursor c; /* cursor */
373 int top; /* top scroll limit */
374 int bot; /* bottom scroll limit */
375 int mode; /* terminal mode flags */
376 int esc; /* escape state flags */
378 TCursor csaved; /* saved cursor info */
379 // old cursor position
380 int oldcx;
381 int oldcy;
383 char title[ESC_TITLE_SIZ+1];
384 int titlelen;
386 int mouseob;
387 int mouseox;
388 int mouseoy;
390 char obuf[OBUFSIZ];
391 #ifdef DUMP_PROG_OUTPUT
392 int xobuflen;
393 #endif
394 int obuflen;
396 char ubuf[UTF_SIZ];
397 int ubufpos;
399 char drawbuf[DRAW_BUF_SIZ];
401 char wrbuf[WBUFSIZ];
402 int wrbufsize;
403 int wrbufused;
404 int wrbufpos;
406 CSIEscape escseq;
407 Selection sel;
408 pid_t pid;
409 int lastDrawTime;
411 char *execcmd;
413 Pixmap picbuf;
414 int picbufw;
415 int picbufh;
417 ushort deffg;
418 ushort defbg;
420 int wantRedraw;
422 int lastActiveTime;
423 } Term;
426 ////////////////////////////////////////////////////////////////////////////////
427 /* Globals */
428 static uchar *unimap = NULL; // 0 or 65535 items; 127: special; high bit set: use alt(gfx) mode; 0: empty
430 static char **opt_cmd = NULL;
431 static char *opt_title = NULL;
432 static char *opt_embed = NULL;
433 static char *opt_class = NULL;
434 static char *opt_term = NULL;
435 static char *opt_fontnorm = NULL;
436 static char *opt_fontbold = NULL;
437 static char *opt_fonttab = NULL;
438 static char *opt_shell = NULL;
439 static char *opt_colornames[512];
440 static int opt_term_locked = 0;
441 static int opt_doubleclick_timeout = DOUBLECLICK_TIMEOUT;
442 static int opt_tripleclick_timeout = TRIPLECLICK_TIMEOUT;
443 static int opt_tabsize = TAB;
444 static int defaultFG = DEFAULT_FG;
445 static int defaultBG = DEFAULT_BG;
446 static int defaultCursorFG = 0;
447 static int defaultCursorBG = DEFAULT_CS;
448 static int defaultCursorInactiveFG = 0;
449 static int defaultCursorInactiveBG = DEFAULT_UCS;
450 static int defaultBoldFG = -1;
451 static int defaultUnderlineFG = -1;
452 static int normalTabFG = 258;
453 static int normalTabBG = 257;
454 static int activeTabFG = 258;
455 static int activeTabBG = 0;
456 static int opt_maxhistory = 512;
457 static int opt_ptrblank = 2000; // delay; 0: never
458 static int opt_tabcount = 6;
459 static int opt_tabposition = 0;
460 static int opt_drawtimeout = DRAW_TIMEOUT;
461 static int ptrBlanked = 0;
462 static int ptrLastMove = 0;
464 static Term **term_array = NULL;
465 static int term_count = 0;
466 static int term_array_size = 0;
467 static Term *term; // current terminal
468 static int termidx; // current terminal index; DON'T RELAY ON IT!
469 static int updateTabBar;
470 static int lastDrawTime = 0;
471 static char *lastSelStr = NULL;
472 //static int lastSelLength = 0;
474 static DC dc;
475 static XWindow xw;
477 static Atom XA_VT_SELECTION;
478 static Atom XA_CLIPBOARD;
479 static Atom XA_UTF8;
480 static Atom XA_TARGETS;
481 static Atom XA_NETWM_NAME;
484 ////////////////////////////////////////////////////////////////////////////////
485 typedef struct {
486 KeySym src;
487 KeySym dst;
488 } KeyTransDef;
491 static KeyTransDef *keytrans = NULL;
492 static int keytrans_size = 0;
493 static int keytrans_used = 0;
496 typedef struct {
497 KeySym key;
498 uint mask;
499 int kp;
500 char *str;
501 } KeyInfoDef;
504 static KeyInfoDef *keybinds = NULL;
505 static int keybinds_size = 0;
506 static int keybinds_used = 0;
508 static KeyInfoDef *keymap = NULL;
509 static int keymap_size = 0;
510 static int keymap_used = 0;
513 ////////////////////////////////////////////////////////////////////////////////
514 static void executeCommands (const char *str);
516 static void ttyresize (void);
517 static void tputc (const char *c); // `c` is utf-8
518 static void ttywrite (const char *s, size_t n);
519 static void tsetdirt (int top, int bot);
520 static void tfulldirt (void);
522 static void xseturgency (int add);
523 static void xfixsel (void);
524 static void xblankPointer (void);
525 static void xunblankPointer (void);
527 static void draw (int forced);
530 static inline void ttywritestr (const char *s) { if (s != NULL && s[0]) ttywrite(s, strlen(s)); }
533 ////////////////////////////////////////////////////////////////////////////////
535 static void trimstr (char *s) {
536 char *e;
538 while (*s && isspace(*s)) ++s;
539 for (e = s+strlen(s); e > s; --e) if (!isspace(e[-1])) break;
540 if (e <= s) *s = 0; else *e = 0;
544 // parse the argument list
545 // return error message or NULL
546 // format:
547 // '*': skip
548 // 's': string (char *)
549 // 'i': integer (int *)
550 // 'b': boolean (int *)
551 // '|': optional arguments follows
552 // '.': stop parsing, ignore rest
553 // 'R': stop parsing, set rest ptr (char *)
554 // string modifiers (also for 'R'):
555 // '!' -- don't allow empty strings
556 // '-' -- trim spaces
557 // int modifiers:
558 // {lo,hi}
559 // {,hi}
560 // {lo}
561 // WARNING! `line` will be modified!
562 // WARNING! string pointers will point INSIDE `line`, so don't discard it!
563 // UGLY! REWRITE!
564 const char *iniParseArguments (char *line, const char *fmt, ...) {
565 va_list ap;
566 int inOptional = 0;
568 if (line == NULL) return "alas";
569 trimstr(line);
570 va_start(ap, fmt);
571 while (*fmt) {
572 char spec = *fmt++, *args;
574 if (spec == '|') { inOptional = 1; continue; }
575 if (spec == '.') { va_end(ap); return NULL; }
577 while (*line && isspace(*line)) ++line;
578 if (*line == '#') *line = 0;
580 if (spec == 'R') {
581 char **p = va_arg(ap, char **);
582 int noempty = 0;
584 while (*fmt) {
585 if (*fmt == '!') { ++fmt; noempty = 1; }
586 else if (*fmt == '-') { ++fmt; trimstr(line); }
587 else break;
589 if (noempty && !line[0]) return "invalid empty arg";
590 if (p != NULL) *p = line;
591 va_end(ap);
592 return NULL;
595 if (!line[0]) {
596 // end of line, stop right here
597 va_end(ap);
598 if (!inOptional) return "out of args";
599 return NULL;
602 args = line;
604 char *dest = args, qch = '#';
605 int n;
607 if (line[0] == '"' || line[0] == '\'') qch = *line++;
609 while (*line && *line != qch) {
610 if (qch == '#' && isspace(*line)) break;
612 if (*line == '\\') {
613 switch (*(++line)) {
614 case 'n': *dest++ = '\n'; ++line; break;
615 case 'r': *dest++ = '\r'; ++line; break;
616 case 't': *dest++ = '\t'; ++line; break;
617 case 'a': *dest++ = '\a'; ++line; break;
618 case 'e': *dest++ = '\x1b'; ++line; break; // esc
619 case 's': *dest++ = ' '; ++line; break;
620 case 'x': // hex
621 ++line;
622 if (!isxdigit(*line)) { va_end(ap); return "invalid hex escape"; }
623 n = toupper(*line)-'0'; if (n > 9) n -= 7;
624 ++line;
625 if (isxdigit(*line)) {
626 int b = toupper(*line)-'0'; if (b > 9) b -= 7;
628 n = (n*16)+b;
629 ++line;
631 *dest++ = n;
632 break;
633 case '0': // octal
634 n = 0;
635 for (int f = 0; f < 4; ++f) {
636 if (*line < '0' || *line > '7') break;
637 n = (n*8)+(line[0]-'0');
638 if (n > 255) { va_end(ap); return "invalid oct escape"; }
639 ++line;
641 if (n == 0) { va_end(ap); return "invalid oct escape"; }
642 *dest++ = n;
643 break;
644 case '1'...'9': // decimal
645 n = 0;
646 for (int f = 0; f < 3; ++f) {
647 if (*line < '0' || *line > '9') break;
648 n = (n*8)+(line[0]-'0');
649 if (n > 255) { va_end(ap); return "invalid dec escape"; }
650 ++line;
652 if (n == 0) { va_end(ap); return "invalid oct escape"; }
653 *dest++ = n;
654 break;
655 default:
656 *dest++ = *line++;
657 break;
659 } else {
660 *dest++ = *line++;
663 if (qch != '#') {
664 if (*line != qch) return "unfinished string";
665 ++line;
666 } else if (*line != '#') ++line;
667 *dest = 0;
669 // now process and convert argument
670 switch (spec) {
671 case '*': /* skip */
672 break;
673 case 's': { /* string */
674 int noempty = 0, trim = 0;
675 char **p;
677 for (;;) {
678 if (*fmt == '!') { noempty = 1; ++fmt; }
679 else if (*fmt == '-') { trim = 1; ++fmt; }
680 else break;
683 if (trim) trimstr(args);
685 if (noempty && !args[0]) { va_end(ap); return "invalid empty string"; }
686 p = va_arg(ap, char **);
687 if (p != NULL) *p = args;
688 } break;
689 case 'i': /* int */
690 if (!args[0]) {
691 va_end(ap);
692 return "invalid integer";
693 } else {
694 int *p = va_arg(ap, int *);
695 long int n;
696 char *eptr;
698 trimstr(args);
699 n = strtol(args, &eptr, 0);
700 if (*eptr) { va_end(ap); return "invalid integer"; }
702 if (*fmt == '{') {
703 // check min/max
704 int minmax[2], haveminmax[2];
706 haveminmax[0] = haveminmax[1] = 0;
707 minmax[0] = minmax[1] = 0;
708 ++fmt;
709 for (int f = 0; f < 2; ++f) {
710 if (isdigit(*fmt) || *fmt == '-' || *fmt == '+') {
711 int neg = 0;
712 haveminmax[f] = 1;
713 if (*fmt == '-') neg = 1;
714 if (!isdigit(*fmt)) {
715 ++fmt;
716 if (!isdigit(*fmt)) { va_end(ap); return "invalid integer bounds"; }
718 while (isdigit(*fmt)) {
719 minmax[f] = minmax[f]*10+(fmt[0]-'0');
720 ++fmt;
722 if (neg) minmax[f] = -minmax[f];
723 //fprintf(stderr, "got: %d\n", minmax[f]);
725 if (*fmt == ',') {
726 if (f == 1) { va_end(ap); return "invalid integer bounds: extra comma"; }
727 // do nothing, we are happy
728 ++fmt;
729 } else if (*fmt == '}') {
730 // ok, done
731 break;
732 } else { va_end(ap); return "invalid integer bounds"; }
734 if (*fmt != '}') { va_end(ap); return "invalid integer bounds"; }
735 ++fmt;
737 //fprintf(stderr, "b: (%d,%d) (%d,%d)\n", haveminmax[0], minmax[0], haveminmax[1], minmax[1]);
738 if ((haveminmax[0] && n < minmax[0]) || (haveminmax[1] && n > minmax[1])) { va_end(ap); return "integer out of bounds"; }
741 if (p) *p = n;
743 break;
744 case 'b': { /* bool */
745 int *p = va_arg(ap, int *);
747 trimstr(args);
748 if (!args[0]) { va_end(ap); return "invalid boolean"; }
749 if (strcasecmp(args, "true") == 0 || strcasecmp(args, "on") == 0 ||
750 strcasecmp(args, "tan") == 0 || strcasecmp(args, "1") == 0) {
751 if (p) *p = 1;
752 } else if (strcasecmp(args, "false") == 0 || strcasecmp(args, "off") == 0 ||
753 strcasecmp(args, "ona") == 0 || strcasecmp(args, "0") == 0) {
755 if (p) *p = 0;
756 } else {
757 va_end(ap);
758 return "invalid boolean";
760 } break;
761 default:
762 va_end(ap);
763 return "invalid format specifier";
766 va_end(ap);
767 while (*line && isspace(*line)) ++line;
768 if (!line[0] || line[0] == '#') return NULL;
769 return "extra args";
773 ////////////////////////////////////////////////////////////////////////////////
774 // UTF-8
775 static int utf8decode (const char *s, long *u) {
776 uchar c;
777 int n, rtn;
779 rtn = 1;
780 c = *s;
781 if (~c & B7) { /* 0xxxxxxx */
782 *u = c;
783 return rtn;
784 } else if ((c & (B7|B6|B5)) == (B7|B6)) { /* 110xxxxx */
785 *u = c&(B4|B3|B2|B1|B0);
786 n = 1;
787 } else if ((c & (B7|B6|B5|B4)) == (B7|B6|B5)) { /* 1110xxxx */
788 *u = c&(B3|B2|B1|B0);
789 n = 2;
790 } else if ((c & (B7|B6|B5|B4|B3)) == (B7|B6|B5|B4)) { /* 11110xxx */
791 *u = c & (B2|B1|B0);
792 n = 3;
793 } else {
794 goto invalid;
796 ++s;
797 for (int f = n; f > 0; --f, ++rtn, ++s) {
798 c = *s;
799 if ((c & (B7|B6)) != B7) goto invalid; /* 10xxxxxx */
800 *u <<= 6;
801 *u |= c & (B5|B4|B3|B2|B1|B0);
803 if ((n == 1 && *u < 0x80) ||
804 (n == 2 && *u < 0x800) ||
805 (n == 3 && *u < 0x10000) ||
806 (*u >= 0xD800 && *u <= 0xDFFF)) {
807 goto invalid;
809 return rtn;
810 invalid:
811 *u = 0xFFFD;
812 return rtn;
816 static int utf8encode (const long *u, char *s) {
817 uchar *sp;
818 ulong uc;
819 int n;
821 sp = (uchar *)s;
822 uc = *u;
823 if (uc < 0x80) {
824 *sp = uc; /* 0xxxxxxx */
825 return 1;
826 } else if (*u < 0x800) {
827 *sp = (uc >> 6) | (B7|B6); /* 110xxxxx */
828 n = 1;
829 } else if (uc < 0x10000) {
830 *sp = (uc >> 12) | (B7|B6|B5); /* 1110xxxx */
831 n = 2;
832 } else if (uc <= 0x10FFFF) {
833 *sp = (uc >> 18) | (B7|B6|B5|B4); /* 11110xxx */
834 n = 3;
835 } else {
836 goto invalid;
838 ++sp;
839 for (int f = n; f > 0; --f, ++sp) *sp = ((uc >> 6*(f-1)) & (B5|B4|B3|B2|B1|B0)) | B7; /* 10xxxxxx */
840 return n+1;
841 invalid:
842 /* U+FFFD */
843 *s++ = '\xEF';
844 *s++ = '\xBF';
845 *s = '\xBD';
846 return 3;
850 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
851 UTF-8 otherwise return 0 */
852 static int isfullutf8 (const char *s, int b) {
853 uchar *c1, *c2, *c3;
855 c1 = (uchar *) s;
856 c2 = (uchar *) ++s;
857 c3 = (uchar *) ++s;
858 if (b < 1) return 0;
859 if ((*c1&(B7|B6|B5)) == (B7|B6) && b == 1) return 0;
860 if ((*c1&(B7|B6|B5|B4)) == (B7|B6|B5) && (b == 1 || (b == 2 && (*c2&(B7|B6)) == B7))) return 0;
861 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;
862 return 1;
866 static int utf8size (const char *s) {
867 uchar c = *s;
869 if (~c&B7) return 1;
870 if ((c&(B7|B6|B5)) == (B7|B6)) return 2;
871 if ((c&(B7|B6|B5|B4)) == (B7|B6|B5)) return 3;
872 return 4;
876 ////////////////////////////////////////////////////////////////////////////////
877 // utilities
878 static char *SPrintfVA (const char *fmt, va_list vaorig) {
879 char *buf = NULL;
880 int olen, len = 128;
882 buf = malloc(len);
883 if (buf == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
884 for (;;) {
885 char *nb;
886 va_list va;
888 va_copy(va, vaorig);
889 olen = vsnprintf(buf, len, fmt, va);
890 va_end(va);
891 if (olen >= 0 && olen < len) return buf;
892 if (olen < 0) olen = len*2-1;
893 nb = realloc(buf, olen+1);
894 if (nb == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
895 buf = nb;
896 len = olen+1;
901 static __attribute__((format(printf,1,2))) char *SPrintf (const char *fmt, ...) {
902 char *buf = NULL;
903 va_list va;
905 va_start(va, fmt);
906 buf = SPrintfVA(fmt, va);
907 va_end(va);
908 return buf;
912 static __attribute__((noreturn)) __attribute__((format(printf,1,2))) void die (const char *errstr, ...) {
913 va_list ap;
915 fprintf(stderr, "FATAL: ");
916 va_start(ap, errstr);
917 vfprintf(stderr, errstr, ap);
918 va_end(ap);
919 fprintf(stderr, "\n");
920 exit(EXIT_FAILURE);
924 ////////////////////////////////////////////////////////////////////////////////
925 // getticks
926 static struct timespec mclk_sttime; // starting time of monotonic clock
929 static void mclock_init (void) {
930 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &mclk_sttime);
934 static uint mclock_ticks (void) {
935 struct timespec tp;
937 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &tp);
938 tp.tv_sec -= mclk_sttime.tv_sec;
939 return tp.tv_sec*1000+(tp.tv_nsec/1000000);
943 ////////////////////////////////////////////////////////////////////////////////
944 // locale conversions
945 static iconv_t icFromLoc;
946 static iconv_t icToLoc;
947 static int needConversion = 0;
950 static void initLCConversion (void) {
951 const char *lct = setlocale(LC_CTYPE, NULL);
952 char *cstr;
954 needConversion = 0;
955 if (strrchr(lct, '.')) lct = strrchr(lct, '.')+1;
956 if (strcasecmp(lct, "utf8") == 0 || strcasecmp(lct, "utf-8") == 0) return;
957 //fprintf(stderr, "locale: [%s]\n", lct);
958 icFromLoc = iconv_open("UTF-8", lct);
959 if (icFromLoc == (iconv_t)-1) die("can't initialize locale conversion");
960 cstr = SPrintf("%s//TRANSLIT", lct);
961 icToLoc = iconv_open(cstr, "UTF-8");
962 free(cstr);
963 if (icToLoc == (iconv_t)-1) die("can't initialize locale conversion");
964 needConversion = 1;
968 static int loc2utf (char *dest, const char *src, int len) {
969 if (needConversion) {
970 char *ibuf, *obuf;
971 size_t il, ol, ool;
973 ibuf = (char *)src;
974 obuf = dest;
975 il = len;
976 ool = ol = il*4;
977 il = iconv(icFromLoc, &ibuf, &il, &obuf, &ol);
978 if (il == (size_t)-1) return 0;
979 return ool-ol;
980 } else {
981 if (len > 0) memmove(dest, src, len);
982 return len;
987 static int utf2loc (char *dest, const char *src, int len) {
988 if (needConversion) {
989 char *ibuf, *obuf;
990 size_t il, ol, ool;
992 ibuf = (char *)src;
993 obuf = dest;
994 il = len;
995 ool = ol = il*4;
996 il = iconv(icToLoc, &ibuf, &il, &obuf, &ol);
997 if (il == (size_t)-1) return 0;
998 return ool-ol;
999 } else {
1000 if (len > 0) memmove(dest, src, len);
1001 return len;
1006 ////////////////////////////////////////////////////////////////////////////////
1007 static void fixWindowTitle (const Term *t) {
1008 const char *title = (t != NULL) ? t->title : NULL;
1010 if (title == NULL || !title[0]) {
1011 title = opt_title;
1012 if (title == NULL) title = "";
1014 XStoreName(xw.dpy, xw.win, title);
1015 XChangeProperty(xw.dpy, xw.win, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, (const unsigned char *)title, strlen(title));
1019 // find latest active terminal (but not current %-)
1020 static int findTermToSwitch (void) {
1021 int maxlat = -1, idx = -1;
1023 for (int f = 0; f < term_count; ++f) {
1024 if (term != term_array[f] && term_array[f]->lastActiveTime > maxlat) {
1025 maxlat = term_array[f]->lastActiveTime;
1026 idx = f;
1029 if (idx < 0) {
1030 if (termidx == 0) idx = 0; else idx = termidx+1;
1031 if (idx > term_count) idx = term_count-1;
1033 return idx;
1037 static void switchToTerm (int idx, int redraw) {
1038 if (idx >= 0 && idx < term_count && term_array[idx] != NULL && term_array[idx] != term) {
1039 if (term != NULL) term->lastActiveTime = mclock_ticks();
1040 termidx = idx;
1041 term = term_array[termidx];
1042 xseturgency(0);
1043 tfulldirt();
1044 fixWindowTitle(term);
1045 updateTabBar = 1;
1046 if (redraw) draw(1);
1047 //FIXME: optimize memory allocations
1048 if (term->sel.clip != NULL && term->sel.bx >= 0) {
1049 if (lastSelStr != NULL) free(lastSelStr);
1050 lastSelStr = strdup(term->sel.clip);
1051 //fprintf(stderr, "newsel: [%s]\n", lastSelStr);
1053 xfixsel();
1054 //fprintf(stderr, "term #%d\n", termidx);
1055 //fprintf(stderr, "needConv: %d\n", term->needConv);
1060 static Term *termalloc (void) {
1061 Term *t;
1063 if (term_count >= term_array_size) {
1064 int newsz = (term_count==0) ? 1 : term_array_size+64;
1065 Term **n = realloc(term_array, sizeof(Term *)*newsz);
1067 if (n == NULL && term_count == 0) die("out of memory!");
1068 term_array = n;
1069 term_array_size = newsz;
1071 if ((t = calloc(1, sizeof(Term))) == NULL) return NULL;
1072 t->wrbufsize = WBUFSIZ;
1073 t->deffg = defaultFG;
1074 t->defbg = defaultBG;
1075 t->dead = 1;
1076 t->needConv = (needConversion ? 1 : 0);
1077 term_array[term_count++] = t;
1078 return t;
1082 // newer delete last terminal!
1083 static void termfree (int idx) {
1084 if (idx >= 0 && idx < term_count && term_array[idx] != NULL) {
1085 Term *t = term_array[idx];
1087 if (t->pid != 0) {
1088 kill(t->pid, SIGKILL);
1089 return;
1091 if (t->cmdfd >= 0) {
1092 close(t->cmdfd);
1093 t->cmdfd = -1;
1095 if (idx == termidx) {
1096 if (term_count > 1) {
1097 t->dead = 1;
1098 //switchToTerm((idx > 0) ? idx-1 : 1, 0);
1099 switchToTerm(findTermToSwitch(), 0);
1100 return;
1102 term = NULL;
1104 for (int y = 0; y < t->row; ++y) free(t->alt[y]);
1105 for (int y = 0; y < t->linecount; ++y) {
1106 //fprintf(stderr, "y=%d\n", y);
1107 free(t->line[y]);
1109 free(t->dirty);
1110 free(t->alt);
1111 free(t->line);
1112 if (t->execcmd != NULL) free(t->execcmd);
1113 // condense array
1114 if (termidx > idx) {
1115 // not current, and current at the right
1116 --termidx;
1118 for (int f = idx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
1119 --term_count;
1120 XFreePixmap(xw.dpy, t->picbuf);
1121 free(t);
1122 updateTabBar = 1;
1123 draw(1);
1128 static void termcleanup (void) {
1129 int f = 0;
1131 while (f < term_count) {
1132 if (term_array[f]->dead) {
1133 termfree(f);
1134 } else {
1135 ++f;
1141 //FIXME: is it safe to assume that signal interrupted main program?
1142 static void sigchld (int a) {
1143 //if (waitpid(term->pid, &stat, 0) < 0) die("waiting for pid %hd failed: %s", term->pid, SERRNO);
1144 for (;;) {
1145 int stat = 0;
1146 pid_t res = waitpid(-1, &stat, WNOHANG);
1148 if (res == (pid_t)-1 || res == 0) break;
1149 //if (WIFEXITED(stat)) exit(WEXITSTATUS(stat));
1150 //exit(EXIT_FAILURE);
1151 for (int f = 0; f < term_count; ++f) {
1152 if (term_array[f]->pid == res) {
1153 // this terminal should die
1154 //if (term_count == 1) exit(0);
1155 //close(term_array[f]->cmdfd);
1156 //term_array[f]->cmdfd = -1;
1157 term_array[f]->dead = 1;
1158 term_array[f]->pid = 0;
1159 updateTabBar = 1;
1160 break;
1164 signal(SIGCHLD, sigchld);
1168 ////////////////////////////////////////////////////////////////////////////////
1169 static void keytrans_reset (void) {
1170 if (keytrans) free(keytrans);
1171 keytrans = NULL;
1172 keytrans_size = 0;
1173 keytrans_used = 0;
1177 static void keytrans_add (const char *src, const char *dst) {
1178 KeySym kssrc = XStringToKeysym(src);
1179 KeySym ksdst = XStringToKeysym(dst);
1181 if (kssrc == NoSymbol) die("invalid keysym: '%s'", src);
1182 if (ksdst == NoSymbol) die("invalid keysym: '%s'", dst);
1183 if (kssrc == ksdst) return; // idiot
1185 for (int f = 0; f < keytrans_used; ++f) {
1186 if (keytrans[f].src == kssrc) {
1187 // replace
1188 keytrans[f].dst = ksdst;
1189 return;
1193 if (keytrans_used >= keytrans_size) {
1194 int newsize = keytrans_size+64;
1195 KeyTransDef *n = realloc(keytrans, sizeof(KeyTransDef)*newsize);
1197 if (n == NULL) die("out of memory");
1198 keytrans_size = newsize;
1199 keytrans = n;
1201 keytrans[keytrans_used].src = kssrc;
1202 keytrans[keytrans_used].dst = ksdst;
1203 ++keytrans_used;
1207 ////////////////////////////////////////////////////////////////////////////////
1208 static void parsekeyname (const char *str, KeySym *ks, uint *mask, int *kp) {
1209 char *s = alloca(strlen(str)+1);
1211 if (s == NULL) die("out of memory");
1212 strcpy(s, str);
1213 *kp = 0;
1214 *ks = NoSymbol;
1215 *mask = XK_NO_MOD;
1217 while (*s) {
1218 char *e, oc;
1219 uint mm = 0;
1220 int mod = 1;
1222 while (*s && isspace(*s)) ++s;
1223 for (e = s; *e && !isspace(*e) && *e != '+'; ++e) ;
1224 oc = *e; *e = 0;
1226 if (strcasecmp(s, "alt") == 0) mm = Mod1Mask;
1227 else if (strcasecmp(s, "win") == 0) mm = Mod4Mask;
1228 else if (strcasecmp(s, "ctrl") == 0) mm = ControlMask;
1229 else if (strcasecmp(s, "shift") == 0) mm = ShiftMask;
1230 else if (strcasecmp(s, "any") == 0) mm = XK_NO_MOD; //!
1231 else if (strcasecmp(s, "kpad") == 0) *kp = 1;
1232 else {
1233 mod = 0;
1234 if ((*ks = XStringToKeysym(s)) == NoSymbol) break;
1235 //fprintf(stderr, "[%s]\n", s);
1238 *e = oc;
1239 s = e;
1240 while (*s && isspace(*s)) ++s;
1241 if (mod) {
1242 if (*s != '+') { *ks = NoSymbol; break; }
1243 ++s;
1244 if (mm != 0) {
1245 if (mm == XK_NO_MOD) *mask = XK_ANY_MOD;
1246 else if (*mask == XK_NO_MOD) *mask = mm;
1247 else if (*mask != XK_ANY_MOD) *mask |= mm;
1249 } else {
1250 if (*s) { *ks = NoSymbol; break; }
1253 if (*ks == NoSymbol) die("invalid key name: '%s'", str);
1254 //fprintf(stderr, "mask=0x%08x, kp=%d\n", *mask, *kp);
1258 ////////////////////////////////////////////////////////////////////////////////
1259 static void keybinds_reset (void) {
1260 if (keybinds) free(keybinds);
1261 keybinds = NULL;
1262 keybinds_size = 0;
1263 keybinds_used = 0;
1267 static void keybind_add (const char *key, const char *act) {
1268 KeySym ks;
1269 uint mask;
1270 int kp;
1272 parsekeyname(key, &ks, &mask, &kp);
1274 for (int f = 0; f < keybinds_used; ++f) {
1275 if (keybinds[f].key == ks && keybinds[f].mask == mask) {
1276 // replace or remove
1277 free(keybinds[f].str);
1278 if (act == NULL || !act[0]) {
1279 // remove
1280 for (int c = f+1; c < keybinds_used; ++c) keybinds[c-1] = keybinds[c];
1281 } else {
1282 // replace
1283 if ((keybinds[f].str = strdup(act)) == NULL) die("out of memory");
1285 return;
1289 if (keybinds_used >= keybinds_size) {
1290 int newsize = keybinds_size+64;
1291 KeyInfoDef *n = realloc(keybinds, sizeof(KeyInfoDef)*newsize);
1293 if (n == NULL) die("out of memory");
1294 keybinds_size = newsize;
1295 keybinds = n;
1297 keybinds[keybinds_used].key = ks;
1298 keybinds[keybinds_used].mask = mask;
1299 keybinds[keybinds_used].kp = 0;
1300 if ((keybinds[keybinds_used].str = strdup(act)) == NULL) die("out of memory");
1301 ++keybinds_used;
1305 ////////////////////////////////////////////////////////////////////////////////
1306 static void keymap_reset (void) {
1307 if (keymap) free(keymap);
1308 keymap = NULL;
1309 keymap_size = 0;
1310 keymap_used = 0;
1314 static void keymap_add (const char *key, const char *act) {
1315 KeySym ks;
1316 uint mask;
1317 int kp;
1319 parsekeyname(key, &ks, &mask, &kp);
1321 for (int f = 0; f < keymap_used; ++f) {
1322 if (keymap[f].key == ks && keymap[f].mask == mask && keymap[f].kp == kp) {
1323 // replace or remove
1324 free(keymap[f].str);
1325 if (act == NULL) {
1326 // remove
1327 for (int c = f+1; c < keymap_used; ++c) keymap[c-1] = keymap[c];
1328 } else {
1329 // replace
1330 if ((keymap[f].str = strdup(act)) == NULL) die("out of memory");
1332 return;
1336 if (keymap_used >= keymap_size) {
1337 int newsize = keymap_size+128;
1338 KeyInfoDef *n = realloc(keymap, sizeof(KeyInfoDef)*newsize);
1340 if (n == NULL) die("out of memory");
1341 keymap_size = newsize;
1342 keymap = n;
1344 keymap[keymap_used].key = ks;
1345 keymap[keymap_used].mask = mask;
1346 keymap[keymap_used].kp = kp;
1347 if ((keymap[keymap_used].str = strdup(act)) == NULL) die("out of memory");
1348 ++keymap_used;
1352 ////////////////////////////////////////////////////////////////////////////////
1353 // selection
1354 static void inline markDirty (int lineno, int flag) {
1355 if (term != NULL && lineno >= 0 && lineno < term->row) {
1356 term->dirty[lineno] |= flag;
1357 term->wantRedraw = 1;
1358 term->lastDrawTime = mclock_ticks(); //FIXME: avoid excess redraw?
1363 static void selinit (void) {
1364 term->sel.tclick1 = term->sel.tclick2 = mclock_ticks();
1365 term->sel.mode = 0;
1366 term->sel.bx = -1;
1367 term->sel.clip = NULL;
1368 term->sel.xtarget = XA_UTF8;
1369 //if (term->sel.xtarget == None) term->sel.xtarget = XA_STRING;
1373 static inline int selected (int x, int y) {
1374 if (term->sel.ey == y && term->sel.by == y) {
1375 int bx = MIN(term->sel.bx, term->sel.ex);
1376 int ex = MAX(term->sel.bx, term->sel.ex);
1378 return BETWEEN(x, bx, ex);
1380 return
1381 ((term->sel.b.y < y && y < term->sel.e.y) || (y == term->sel.e.y && x <= term->sel.e.x)) ||
1382 (y == term->sel.b.y && x >= term->sel.b.x && (x <= term->sel.e.x || term->sel.b.y != term->sel.e.y));
1386 static void getbuttoninfo (XEvent *e, int *b, int *x, int *y) {
1387 if (b) *b = e->xbutton.button;
1388 *x = X2COL(e->xbutton.x);
1389 *y = Y2ROW(e->xbutton.y);
1390 term->sel.b.x = term->sel.by < term->sel.ey ? term->sel.bx : term->sel.ex;
1391 term->sel.b.y = MIN(term->sel.by, term->sel.ey);
1392 term->sel.e.x = term->sel.by < term->sel.ey ? term->sel.ex : term->sel.bx;
1393 term->sel.e.y = MAX(term->sel.by, term->sel.ey);
1397 static void mousereport (XEvent *e) {
1398 int x = X2COL(e->xbutton.x);
1399 int y = Y2ROW(e->xbutton.y);
1400 int button = e->xbutton.button;
1401 int state = e->xbutton.state;
1402 //char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
1403 char buf[16];
1405 if (term == NULL) return;
1406 if (x < 0 || x >= term->col || y < 0 || y >= term->row) return;
1407 sprintf(buf, "\x1b[M%c%c%c", 0, 32+x+1, 32+y+1);
1408 /* from urxvt */
1409 if (e->xbutton.type == MotionNotify) {
1410 if (!IS_SET(MODE_MOUSEMOTION) || (x == term->mouseox && y == term->mouseoy)) return;
1411 button = term->mouseob+32;
1412 term->mouseox = x;
1413 term->mouseoy = y;
1414 } else if (e->xbutton.type == ButtonRelease || button == AnyButton) {
1415 button = 3;
1416 } else {
1417 button -= Button1;
1418 if (button >= 3) button += 64-3;
1419 if (e->xbutton.type == ButtonPress) {
1420 term->mouseob = button;
1421 term->mouseox = x;
1422 term->mouseoy = y;
1425 buf[3] = 32+button+(state & ShiftMask ? 4 : 0)+(state & Mod4Mask ? 8 : 0)+(state & ControlMask ? 16 : 0);
1426 ttywrite(buf, 6);
1430 static void xfixsel (void) {
1431 if (lastSelStr != NULL) {
1432 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
1433 XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, xw.win, CurrentTime);
1434 } else {
1435 if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) == xw.win) XSetSelectionOwner(xw.dpy, XA_PRIMARY, None, CurrentTime);
1436 if (XGetSelectionOwner(xw.dpy, XA_CLIPBOARD) == xw.win) XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, None, CurrentTime);
1438 XFlush(xw.dpy);
1442 static void xsetsel (char *str) {
1443 /* register the selection for both the clipboard and the primary */
1444 if (term == NULL) return;
1445 if (term->sel.clip != NULL) free(term->sel.clip);
1446 term->sel.clip = str;
1447 if (lastSelStr != NULL) free(lastSelStr);
1448 lastSelStr = (str != NULL ? strdup(str) : NULL);
1449 xfixsel();
1450 //fprintf(stderr, "[%s]\n", str);
1454 static Line selgetlinebyy (int y) {
1455 Line l;
1457 if (y >= term->row) return NULL;
1458 if (y < 0) {
1459 if (y <= -(term->maxhistory)) return NULL;
1460 l = term->line[term->row+(-y)-1];
1461 } else {
1462 l = term->line[y];
1464 return l;
1468 static void selcopy (void) {
1469 char *str, *ptr;
1470 int x, y, bufsize, is_selected = 0;
1472 if (term == NULL || term->sel.bx == -1) {
1473 str = NULL;
1474 } else {
1475 int sy = MIN(term->sel.e.y, term->sel.b.y);
1476 int ey = MAX(term->sel.e.y, term->sel.b.y);
1479 fprintf(stderr, "bx=%d; ex=%d; by=%d; ey=%d\n", term->sel.bx, term->sel.by, term->sel.ex, term->sel.ey);
1480 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);
1481 fprintf(stderr, " sy=%d; ey=%d\n", sy, ey);
1483 bufsize = (term->col+1)*(ey-sy+1)*UTF_SIZ;
1484 ptr = str = malloc(bufsize);
1485 /* append every set & selected glyph to the selection */
1486 for (y = sy; y <= ey; ++y) {
1487 Line l = selgetlinebyy(y);
1489 if (y >= term->row) break;
1490 if (l == NULL) continue;
1491 for (x = 0; x < term->col; ++x) {
1492 is_selected = selected(x, y);
1493 //if (is_selected) term->line[y][x].state |= GLYPH_DIRTY;
1494 if (/*(l[x].state & GLYPH_SET) &&*/ is_selected) {
1495 int size = utf8size(l[x].c);
1497 if (size == 1 && l[x].c[0] == 0) {
1498 *ptr = ' ';
1499 } else {
1500 memcpy(ptr, l[x].c, size);
1502 ptr += size;
1505 /* \n at the end of every selected line except for the last one */
1506 if (is_selected && y < term->sel.e.y) {
1507 *ptr++ = '\n';
1510 *ptr = 0;
1512 xsetsel(str);
1516 static void selnotify (XEvent *e) {
1517 ulong nitems, ofs, rem;
1518 int format;
1519 uchar *data;
1520 Atom type;
1521 XSelectionEvent *se = (XSelectionEvent *)e;
1522 int isutf8;
1524 if (term == NULL) return;
1525 if (se->property != XA_VT_SELECTION) return;
1526 if (se->target == XA_UTF8) {
1527 isutf8 = 1;
1528 } else if (se->target == XA_STRING) {
1529 isutf8 = 0;
1530 } else {
1531 return;
1533 if (!isutf8) return;
1534 ofs = 0;
1535 do {
1536 if (XGetWindowProperty(xw.dpy, xw.win, se->property, ofs, BUFSIZ/4, False, AnyPropertyType, &type, &format, &nitems, &rem, &data)) {
1537 fprintf(stderr, "Clipboard allocation failed\n");
1538 return;
1540 //fprintf(stderr, "nitems=%d; format=%d; rem=%d\n", (int)nitems, format, (int)rem);
1541 ttywrite((const char *)data, nitems*format/8);
1542 XFree(data);
1543 /* number of 32-bit chunks returned */
1544 ofs += nitems*format/32;
1545 } while (rem > 0);
1549 static void selpaste (Atom which) {
1550 if (term == NULL) return;
1551 if (XGetSelectionOwner(xw.dpy, which) != None) {
1552 XConvertSelection(xw.dpy, which, term->sel.xtarget, XA_VT_SELECTION, xw.win, CurrentTime);
1553 return;
1555 if (which == XA_PRIMARY) selpaste(XA_SECONDARY);
1556 else if (which == XA_SECONDARY) selpaste(XA_CLIPBOARD);
1560 static void selrequest (XEvent *e) {
1561 XSelectionRequestEvent *xsre;
1562 XSelectionEvent xev;
1564 if (lastSelStr == NULL) return;
1565 xsre = (XSelectionRequestEvent *)e;
1566 xev.type = SelectionNotify;
1567 xev.requestor = xsre->requestor;
1568 xev.selection = xsre->selection;
1569 xev.target = xsre->target;
1570 xev.time = xsre->time;
1571 /* reject */
1572 xev.property = None;
1573 if (xsre->target == XA_TARGETS) {
1574 /* respond with the supported type */
1575 Atom string = XA_UTF8;
1577 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_ATOM, 32, PropModeReplace, (uchar *)&string, 1);
1578 xev.property = xsre->property;
1579 } else if (xsre->target == XA_UTF8 && lastSelStr != NULL) {
1580 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_UTF8, 8, PropModeReplace, (uchar *)lastSelStr, strlen(lastSelStr));
1581 xev.property = xsre->property;
1582 } else if (xsre->target == XA_STRING && lastSelStr != NULL) {
1583 char *s = malloc(strlen(lastSelStr)*4+8);
1585 if (s != NULL) {
1586 int len = utf2loc(s, lastSelStr, strlen(lastSelStr));
1588 XChangeProperty(xsre->display, xsre->requestor, xsre->property, xsre->target, 8, PropModeReplace, (uchar *)s, len);
1589 xev.property = xsre->property;
1590 free(s);
1593 /* all done, send a notification to the listener */
1594 if (!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *)&xev)) fprintf(stderr, "Error sending SelectionNotify event\n");
1598 static void selclear (XEvent *e) {
1599 if (lastSelStr != NULL) free(lastSelStr);
1600 lastSelStr = NULL;
1601 if (term != NULL) {
1602 if (term->sel.clip != NULL) free(term->sel.clip);
1603 term->sel.clip = NULL;
1604 term->sel.mode = 0;
1605 term->sel.ex = term->sel.bx = -1;
1606 term->sel.ey = term->sel.by = -1;
1607 tfulldirt();
1608 draw(1);
1613 static void bpress (XEvent *e) {
1614 if (term == NULL) return;
1616 switch (opt_tabposition) {
1617 case 0: // bottom
1618 if (e->xbutton.y >= xw.h-xw.tabheight) return;
1619 break;
1620 case 1: // top
1621 if (e->xbutton.y < xw.tabheight) return;
1622 break;
1625 if (e->xbutton.button == Button1 && (e->xbutton.state&ShiftMask) != 0) {
1626 if (term->sel.bx != -1) tsetdirt(term->sel.b.y, term->sel.e.y);
1627 term->sel.mode = 1;
1628 term->sel.ex = term->sel.bx = X2COL(e->xbutton.x);
1629 term->sel.ey = term->sel.by = Y2ROW(e->xbutton.y);
1630 //fprintf(stderr, "x=%d; y=%d\n", term->sel.bx, term->sel.by);
1631 draw(1);
1632 return;
1634 if (e->xbutton.button == Button3 && (e->xbutton.state&ShiftMask) != 0) {
1635 term->sel.bx = -1;
1636 selcopy();
1637 draw(1);
1639 if (IS_SET(MODE_MOUSE)) mousereport(e);
1643 static void brelease (XEvent *e) {
1644 int reportit = 1;
1646 switch (opt_tabposition) {
1647 case 0: // bottom
1648 if (e->xbutton.y >= xw.h-xw.tabheight) return;
1649 break;
1650 case 1: // top
1651 if (e->xbutton.y < xw.tabheight) return;
1652 break;
1655 if (term == NULL) return;
1657 if ((e->xbutton.state&ShiftMask) != 0) reportit = 0;
1658 if (reportit && IS_SET(MODE_MOUSE)) {
1659 mousereport(e);
1660 return;
1662 if (e->xbutton.button == Button2) {
1663 selpaste(XA_PRIMARY);
1664 } else if (e->xbutton.button == Button1) {
1665 term->sel.mode = 0;
1666 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey);
1667 if (term->sel.bx == term->sel.ex && term->sel.by == term->sel.ey) {
1668 int now;
1670 markDirty(term->sel.ey, 2);
1671 term->sel.bx = -1;
1672 now = mclock_ticks();
1673 if (now-term->sel.tclick2 <= opt_tripleclick_timeout) {
1674 /* triple click on the line */
1675 term->sel.b.x = term->sel.bx = 0;
1676 term->sel.e.x = term->sel.ex = term->col;
1677 term->sel.b.y = term->sel.e.y = term->sel.ey;
1678 selcopy();
1679 draw(1);
1680 } else if (now-term->sel.tclick1 <= opt_doubleclick_timeout) {
1681 /* double click to select word */
1682 Line l = selgetlinebyy(term->sel.ey);
1684 if (l != NULL) {
1685 //FIXME: write better word selection code
1686 term->sel.bx = term->sel.ex;
1687 while (term->sel.bx > 0 && /*l[term->sel.bx-1].state & GLYPH_SET &&*/ l[term->sel.bx-1].c[0] != ' ') --term->sel.bx;
1688 term->sel.b.x = term->sel.bx;
1689 while (term->sel.ex < term->col-1 && /*l[term->sel.ex+1].state & GLYPH_SET &&*/ l[term->sel.ex+1].c[0] != ' ') ++term->sel.ex;
1690 term->sel.e.x = term->sel.ex;
1691 term->sel.b.y = term->sel.e.y = term->sel.ey;
1692 selcopy();
1693 draw(1);
1696 } else {
1697 selcopy();
1700 term->sel.tclick2 = term->sel.tclick1;
1701 term->sel.tclick1 = mclock_ticks();
1702 //draw(1);
1706 static void bmotion (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 (term->sel.mode) {
1719 int oldey = term->sel.ey, oldex = term->sel.ex;
1721 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey);
1722 if (oldey != term->sel.ey || oldex != term->sel.ex) {
1723 int starty = MIN(oldey, term->sel.ey);
1724 int endy = MAX(oldey, term->sel.ey);
1726 tsetdirt(starty, endy);
1727 draw(1);
1729 return;
1731 if (IS_SET(MODE_MOUSE)) {
1732 mousereport(e);
1733 return;
1738 ////////////////////////////////////////////////////////////////////////////////
1739 // tty init
1740 static inline void setWantRedraw (void) { if (term != NULL) term->wantRedraw = 1; }
1744 static void dump (char c) {
1745 static int col;
1747 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
1748 if (++col % 10 == 0) fprintf(stderr, "\n");
1753 static __attribute__((noreturn)) void execsh (const char *str) {
1754 char **args;
1756 if (str == NULL) {
1757 char *envshell = getenv("SHELL");
1759 DEFAULT(envshell, opt_shell);
1760 setenv("TERM", opt_term, 1);
1761 args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL};
1762 } else {
1763 int argc = 0;
1765 args = calloc(32768, sizeof(char *));
1766 if (args == NULL) exit(EXIT_FAILURE);
1767 while (*str) {
1768 const char *b;
1770 while (*str && isspace(*str)) ++str;
1771 if (!str[0]) break;
1773 b = str;
1774 while (*str && !isspace(*str)) {
1775 if (*str++ == '\\') {
1776 if (*str) ++str;
1780 args[argc] = calloc(str-b+1, 1);
1781 memcpy(args[argc], b, str-b);
1784 FILE *fo = fopen("z.log", "a");
1785 fprintf(fo, "%d: [%s]\n", argc, args[argc]);
1786 fclose(fo);
1789 ++argc;
1791 if (argc < 1) exit(EXIT_FAILURE);
1793 execvp(args[0], args);
1794 exit(EXIT_FAILURE);
1798 static int ttynew (Term *term) {
1799 int m, s;
1800 struct winsize w = {term->row, term->col, 0, 0};
1801 static int signalset = 0;
1803 if (openpty(&m, &s, NULL, NULL, &w) < 0) die("openpty failed: %s", SERRNO);
1804 term->cmdfd = m;
1805 ttyresize();
1806 term->cmdfd = -1;
1807 switch (term->pid = fork()) {
1808 case -1: /* error */
1809 fprintf(stderr, "fork failed");
1810 return -1;
1811 case 0: /* child */
1812 setsid(); /* create a new process group */
1813 dup2(s, STDIN_FILENO);
1814 dup2(s, STDOUT_FILENO);
1815 dup2(s, STDERR_FILENO);
1816 if (ioctl(s, TIOCSCTTY, NULL) < 0) die("ioctl TIOCSCTTY failed: %s", SERRNO);
1817 close(s);
1818 close(m);
1819 execsh(term->execcmd);
1820 break;
1821 default: /* master */
1822 close(s);
1823 term->cmdfd = m;
1824 term->dead = 0;
1825 ttyresize();
1826 if (!signalset) { signalset = 1; signal(SIGCHLD, sigchld); }
1827 break;
1829 return 0;
1833 ////////////////////////////////////////////////////////////////////////////////
1834 // tty r/w
1835 static int ttycanread (void) {
1836 for (;;) {
1837 fd_set rfd;
1838 struct timeval timeout = {0};
1840 if (term->dead || term->cmdfd < 0) return 0;
1841 FD_ZERO(&rfd);
1842 FD_SET(term->cmdfd, &rfd);
1843 if (select(term->cmdfd+1, &rfd, NULL, NULL, &timeout) < 0) {
1844 if (errno == EINTR) continue;
1845 die("select failed: %s", SERRNO);
1847 if (FD_ISSET(term->cmdfd, &rfd)) return 1;
1848 break;
1850 return 0;
1854 static int ttycanwrite (void) {
1855 for (;;) {
1856 fd_set wfd;
1857 struct timeval timeout = {0};
1859 if (term->dead || term->cmdfd < 0) return 0;
1860 FD_ZERO(&wfd);
1861 FD_SET(term->cmdfd, &wfd);
1862 if (select(term->cmdfd+1, NULL, &wfd, NULL, &timeout) < 0) {
1863 if (errno == EINTR) continue;
1864 die("select failed: %s", SERRNO);
1866 if (FD_ISSET(term->cmdfd, &wfd)) return 1;
1867 break;
1869 return 0;
1873 #ifdef DUMP_IO
1874 static void wrstr (const char *s, int len) {
1875 if (s == NULL) return;
1876 while (len-- > 0) {
1877 unsigned char c = (unsigned char)(*s++);
1879 if (c < 32) fprintf(stderr, "{%u}", c); else fwrite(&c, 1, 1, stderr);
1882 #endif
1885 static void ttyread (void) {
1886 char *ptr;
1887 int left;
1889 /* append read bytes to unprocessed bytes */
1890 if (term == NULL || term->dead || term->cmdfd < 0) return;
1891 #ifdef DUMP_PROG_OUTPUT
1892 term->xobuflen = term->obuflen;
1893 #endif
1894 left = OBUFSIZ-term->obuflen;
1895 //dlogf("0: ttyread before: free=%d, used=%d", left, term->obuflen);
1896 while (left > 0 && ttycanread()) {
1897 int ret;
1899 //if ((ret = read(term->cmdfd, term->obuf+term->obuflen, 1)) < 0) die("Couldn't read from shell: %s", SERRNO);
1900 if ((ret = read(term->cmdfd, term->obuf+term->obuflen, left)) < 0) {
1901 //fprintf(stderr, "Warning: couldn't read from tty #%d: %s\n", termidx, SERRNO);
1902 break;
1904 term->obuflen += ret;
1905 left -= ret;
1907 //dlogf("1: ttyread after: free=%d, used=%d", left, term->obuflen);
1908 /* process every complete utf8 char */
1909 #ifdef DUMP_PROG_OUTPUT
1911 FILE *fo = fopen("zlogo.log", "ab");
1912 if (fo) {
1913 fwrite(term->obuf+term->xobuflen, term->obuflen-term->xobuflen, 1, fo);
1914 fclose(fo);
1917 #endif
1918 ptr = term->obuf;
1919 if (term->needConv) {
1920 // need conversion from locale to utf-8
1921 //fprintf(stderr, "buf: %d bytes\n", term->obuflen);
1922 while (term->obuflen > 0) {
1923 char obuf[UTF_SIZ+1];
1924 int len;
1926 len = loc2utf(obuf, ptr, 1);
1927 #ifdef DUMP_IO_READ
1929 fprintf(stderr, "rdc: [");
1930 wrstr(ptr, 1);
1931 fprintf(stderr, "] --> [");
1932 wrstr(obuf, len);
1933 fprintf(stderr, "]\n");
1934 fflush(stderr);
1936 #endif
1937 if (len > 0) {
1938 obuf[len] = 0;
1939 tputc(obuf);
1941 ++ptr;
1942 --term->obuflen;
1944 term->obuflen = 0;
1945 } else {
1946 // don't do any conversion
1947 while (term->obuflen >= UTF_SIZ || isfullutf8(ptr, term->obuflen)) {
1948 long utf8c;
1949 char s[UTF_SIZ+1];
1950 int charsize = utf8decode(ptr, &utf8c); /* returns size of utf8 char in bytes */
1951 int len;
1953 len = utf8encode(&utf8c, s);
1954 #ifdef DUMP_IO_READ
1956 fprintf(stderr, "rdx: [");
1957 wrstr(s, len);
1958 fprintf(stderr, "]\n");
1959 fflush(stderr);
1961 #endif
1962 if (len > 0) {
1963 s[len] = 0;
1964 tputc(s);
1966 ptr += charsize;
1967 term->obuflen -= charsize;
1969 //dlogf("2: ttyread afterproc: used=%d", term->obuflen);
1971 /* keep any uncomplete utf8 char for the next call */
1972 if (term->obuflen > 0) memmove(term->obuf, ptr, term->obuflen);
1976 static void ttyflushwrbuf (void) {
1977 if (term == NULL || term->dead || term->cmdfd < 0) return;
1978 if (term->wrbufpos >= term->wrbufused) {
1979 term->wrbufpos = term->wrbufused = 0;
1980 return;
1982 //dlogf("0: ttyflushwrbuf before: towrite=%d", term->wrbufused-term->wrbufpos);
1983 while (term->wrbufpos < term->wrbufused && ttycanwrite()) {
1984 int ret;
1986 if ((ret = write(term->cmdfd, term->wrbuf+term->wrbufpos, term->wrbufused-term->wrbufpos)) == -1) {
1987 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
1989 term->wrbufpos += ret;
1991 if (term->wrbufpos > 0) {
1992 int left = term->wrbufused-term->wrbufpos;
1994 if (left < 1) {
1995 // write buffer is empty
1996 term->wrbufpos = term->wrbufused = 0;
1997 } else {
1998 memmove(term->wrbuf, term->wrbuf+term->wrbufpos, left);
1999 term->wrbufpos = 0;
2000 term->wrbufused = left;
2003 //dlogf("1: ttyflushwrbuf after: towrite=%d", term->wrbufused-term->wrbufpos);
2007 // convert char to locale and write it
2008 static void ttywriterawchar (const char *s, int len) {
2009 char loc[16];
2010 int clen;
2012 if (s == NULL || len < 1) return;
2013 if (term->needConv) {
2014 if ((clen = utf2loc(loc, s, len)) < 1) return;
2015 } else {
2016 if ((clen = utf8size(s)) < 1) return;
2017 memmove(loc, s, clen);
2019 #ifdef DUMP_IO_WRITE
2021 fprintf(stderr, "wrc: [");
2022 wrstr(s, len);
2023 fprintf(stderr, "] --> [");
2024 wrstr(loc, clen);
2025 fprintf(stderr, "]\n");
2026 fflush(stderr);
2028 #endif
2030 while (term->wrbufused+clen >= term->wrbufsize) {
2031 //FIXME: make write buffer dynamic?
2032 // force write at least one char
2033 //dlogf("ttywrite: forced write");
2034 if (write(term->cmdfd, term->wrbuf+term->wrbufpos, 1) == -1) {
2035 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2036 } else {
2037 ++term->wrbufpos;
2039 ttyflushwrbuf(); // make room for char
2041 memcpy(term->wrbuf+term->wrbufused, loc, clen);
2042 term->wrbufused += clen;
2046 static void ttywrite (const char *s, size_t n) {
2047 if (term == NULL || term->dead || term->cmdfd < 0) return;
2048 #ifdef DUMP_PROG_INPUT
2049 if (s != NULL && n > 0) {
2050 FILE *fo = fopen("zlogw.log", "ab");
2051 if (fo) {
2052 fwrite(s, n, 1, fo);
2053 fclose(fo);
2056 #endif
2057 //ttyflushwrbuf();
2058 if (s != NULL && n > 0) {
2059 while (n > 0) {
2060 unsigned char c = (unsigned char)(s[0]);
2062 if (term->ubufpos > 0 && isfullutf8(term->ubuf, term->ubufpos)) {
2063 // have complete char
2064 ttywriterawchar(term->ubuf, term->ubufpos);
2065 term->ubufpos = 0;
2066 continue;
2069 if (term->ubufpos == 0) {
2070 // new char
2071 if (c < 128) {
2072 ttywriterawchar(s, 1);
2073 } else if ((c&0xc0) == 0xc0) {
2074 // new utf-8 char
2075 term->ubuf[term->ubufpos++] = *s;
2076 } else {
2077 // ignore unsynced utf-8
2079 ++s;
2080 --n;
2081 continue;
2083 // char continues
2084 if (c < 128 || term->ubufpos >= UTF_SIZ || (c&0xc0) == 0xc0) {
2085 // discard previous utf-8, it's bad
2086 term->ubufpos = 0;
2087 continue;
2089 // collect
2090 term->ubuf[term->ubufpos++] = *s;
2091 ++s;
2092 --n;
2093 if (isfullutf8(term->ubuf, term->ubufpos)) {
2094 // have complete char
2095 ttywriterawchar(term->ubuf, term->ubufpos);
2096 term->ubufpos = 0;
2100 ttyflushwrbuf();
2104 ////////////////////////////////////////////////////////////////////////////////
2105 // tty resize ioctl
2106 static void ttyresize (void) {
2107 struct winsize w;
2109 if (term != NULL && term->cmdfd >= 0) {
2110 w.ws_row = term->row;
2111 w.ws_col = term->col;
2112 w.ws_xpixel = w.ws_ypixel = 0;
2113 if (ioctl(term->cmdfd, TIOCSWINSZ, &w) < 0) fprintf(stderr, "Warning: couldn't set window size: %s\n", SERRNO);
2114 setWantRedraw();
2119 ////////////////////////////////////////////////////////////////////////////////
2120 // tty utilities
2121 static void csidump (void) {
2122 printf("ESC");
2123 for (int f = 1; f < term->escseq.len; ++f) {
2124 uint c = (term->escseq.buf[f]&0xff);
2126 if (isprint(c)) putchar(c);
2127 else if (c == '\n') printf("(\\n)");
2128 else if (c == '\r') printf("(\\r)");
2129 else if (c == 0x1b) printf("(\\e)");
2130 else printf("(%02x)", c);
2132 putchar('\n');
2136 static void tsetdirt (int top, int bot) {
2137 LIMIT(top, 0, term->row-1);
2138 LIMIT(bot, 0, term->row-1);
2139 for (int y = top; y <= bot; ++y) markDirty(y, 2);
2143 static void tfulldirt (void) {
2144 tsetdirt(0, term->row-1);
2148 static void tmoveto (int x, int y) {
2149 LIMIT(x, 0, term->col-1);
2150 LIMIT(y, 0, term->row-1);
2151 term->c.state &= ~CURSOR_WRAPNEXT;
2152 if (term->c.x != x || term->c.y != y) {
2153 term->c.x = x;
2154 term->c.y = y;
2155 setWantRedraw();
2160 static void tclearregion (int x1, int y1, int x2, int y2) {
2161 int temp;
2163 if (x1 > x2) { temp = x1; x1 = x2; x2 = temp; }
2164 if (y1 > y2) { temp = y1; y1 = y2; y2 = temp; }
2165 LIMIT(x1, 0, term->col-1);
2166 LIMIT(x2, 0, term->col-1);
2167 LIMIT(y1, 0, term->row-1);
2168 LIMIT(y2, 0, term->row-1);
2169 for (int y = y1; y <= y2; ++y) {
2170 markDirty(y, (x1 <= 0 && x2 >= term->col-1) ? 2 : 1);
2171 for (int x = x1; x <= x2; ++x) {
2172 term->line[y][x].fg = term->c.attr.fg;
2173 term->line[y][x].bg = term->c.attr.bg;
2174 term->line[y][x].state = GLYPH_DIRTY;
2175 term->line[y][x].mode = ATTR_NULL|(term->c.attr.mode&(ATTR_DEFFG|ATTR_DEFBG));
2176 term->line[y][x].c[0] = ' ';
2182 static void tcursor (int mode) {
2183 if (mode == CURSOR_SAVE) {
2184 term->csaved = term->c;
2185 } else if (mode == CURSOR_LOAD) {
2186 term->c = term->csaved;
2187 tmoveto(term->c.x, term->c.y);
2188 setWantRedraw();
2193 static void treset (void) {
2194 Glyph g;
2196 term->c = (TCursor){{
2197 .mode = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG,
2198 .fg = 0,
2199 .bg = 0
2200 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
2201 term->c.attr.fg = term->deffg;
2202 term->c.attr.bg = term->defbg;
2204 g.state = GLYPH_DIRTY;
2205 g.mode = term->c.attr.mode;
2206 g.fg = term->c.attr.fg;
2207 g.bg = term->c.attr.bg;
2208 g.c[0] = ' ';
2209 g.c[1] = 0;
2211 term->top = 0;
2212 term->bot = term->row-1;
2213 term->mode = MODE_WRAP/* | MODE_MOUSEBTN*/;
2214 //tclearregion(0, 0, term->col-1, term->row-1);
2215 for (int y = 0; y < term->row; ++y) {
2216 markDirty(y, 2);
2217 for (int x = 0; x < term->col; ++x) term->alt[y][x] = term->line[y][x] = g;
2219 for (int y = term->row; y < term->linecount; ++y) {
2220 for (int x = 0; x < term->col; ++x) term->line[y][x] = g;
2222 tcursor(CURSOR_SAVE);
2223 term->topline = 0;
2224 tfulldirt();
2228 static int tinitialize (int col, int row) {
2229 //memset(term, 0, sizeof(Term));
2230 //term->needConv = needConversion ? 1 : 0;
2231 term->wrbufsize = WBUFSIZ;
2232 term->deffg = term->deffg;
2233 term->defbg = term->defbg;
2234 term->row = row;
2235 term->col = col;
2236 term->dirty = calloc(term->row, sizeof(*term->dirty));
2237 term->maxhistory = opt_maxhistory;
2238 term->linecount = term->maxhistory+term->row;
2239 term->line = calloc(term->linecount, sizeof(Line));
2240 term->alt = calloc(term->row, sizeof(Line));
2241 for (int y = 0; y < term->linecount; ++y) term->line[y] = calloc(term->col, sizeof(Glyph));
2242 for (int y = 0; y < term->row; ++y) term->alt[y] = calloc(term->col, sizeof(Glyph));
2243 /* setup screen */
2244 treset();
2245 return 1;
2249 static void tswapscreen (void) {
2250 for (int f = 0; f < term->row; ++f) {
2251 Line t = term->line[f];
2253 term->line[f] = term->alt[f];
2254 term->alt[f] = t;
2256 term->mode ^= MODE_ALTSCREEN;
2257 tfulldirt();
2261 //FIXME: will not work for history
2262 static void selscroll (int orig, int n) {
2263 if (term->sel.bx == -1) return;
2265 if (BETWEEN(term->sel.by, orig, term->bot) || BETWEEN(term->sel.ey, orig, term->bot)) {
2266 if ((term->sel.by += n) > term->bot || (term->sel.ey += n) < term->top) {
2267 term->sel.bx = -1;
2268 return;
2270 if (term->sel.by < term->top) {
2271 term->sel.by = term->top;
2272 term->sel.bx = 0;
2274 if (term->sel.ey > term->bot) {
2275 term->sel.ey = term->bot;
2276 term->sel.ex = term->col;
2278 term->sel.b.y = term->sel.by, term->sel.b.x = term->sel.bx;
2279 term->sel.e.y = term->sel.ey, term->sel.e.x = term->sel.ex;
2284 static void tscrolldown (int orig, int n) {
2285 Line temp;
2287 LIMIT(n, 0, term->bot-orig+1);
2288 //fprintf(stderr, "tscrolldown(%d, %d)\n", orig, n);
2289 tclearregion(0, term->bot-n+1, term->col-1, term->bot);
2290 for (int f = term->bot; f >= orig+n; --f) {
2291 temp = term->line[f];
2292 term->line[f] = term->line[f-n];
2293 term->line[f-n] = temp;
2294 markDirty(f, 2);
2295 markDirty(f-n, 2);
2297 selscroll(orig, n);
2301 static void tscrollup (int orig, int n) {
2302 Line temp;
2304 if (term == NULL) return;
2305 LIMIT(n, 0, term->bot-orig+1);
2306 if (n < 1) return;
2307 //fprintf(stderr, "tscrollup(%d, %d)\n", orig, n);
2308 if (n == 1 && orig == 0 && term->bot >= term->row-1 && term->maxhistory > 0) {
2309 //FIXME: move selection instead of resetting
2310 Line l = term->line[term->linecount-1];
2312 for (int f = term->linecount-1; f > term->row; --f) term->line[f] = term->line[f-1];
2313 term->line[term->row] = l;
2314 for (int x = 0; x < term->col; ++x) l[x] = term->line[0][x];
2315 if (term->sel.clip != NULL) {
2316 selclear(NULL);
2317 xfixsel();
2321 tclearregion(0, orig, term->col-1, orig+n-1);
2323 for (int f = orig; f <= term->bot-n; ++f) {
2324 temp = term->line[f];
2325 term->line[f] = term->line[f+n];
2326 term->line[f+n] = temp;
2327 markDirty(f, 2);
2328 markDirty(f+n, 2);
2330 selscroll(orig, -n);
2334 static void tnewline (int first_col) {
2335 int y = term->c.y;
2337 if (y == term->bot) tscrollup(term->top, 1); else ++y;
2338 tmoveto(first_col ? 0 : term->c.x, y);
2342 static void csiparse (void) {
2343 const char *p = term->escseq.buf;
2345 term->escseq.narg = 0;
2346 if (*p == '?') { term->escseq.priv = 1; ++p; }
2347 while (p < term->escseq.buf+term->escseq.len) {
2348 int n = term->escseq.arg[term->escseq.narg];
2350 for (; *p && isdigit(*p); ++p) n = n*10+(p[0]-'0');
2351 term->escseq.arg[term->escseq.narg] = n;
2353 if (*p == ';' && term->escseq.narg+1 < ESC_ARG_SIZ) {
2354 ++term->escseq.narg;
2355 ++p;
2356 } else {
2357 term->escseq.mode = *p;
2358 ++term->escseq.narg;
2359 break;
2365 static void tsetchar (const char *c) {
2366 char ub[UTF_SIZ+1];
2367 int rev = 0, gfx = 0;
2368 int x = term->c.x, y = term->c.y;
2370 if (!term->needConv && unimap != NULL) {
2371 long cc;
2373 utf8decode(c, &cc);
2374 if (cc >= 0 && cc <= 65535) {
2375 uchar uc = unimap[cc];
2377 if (uc) {
2378 if (uc == 127) {
2379 // inversed space
2380 rev = 1;
2381 ub[0] = ' ';
2382 } else {
2383 if (uc&0x80) {
2384 ub[0] = (uc&0x7f);
2385 gfx = 1;
2386 } else {
2387 ub[0] = uc&0x7f;
2390 ub[1] = 0;
2391 c = ub;
2396 if (rev || gfx || (term->line[y][x].state & GLYPH_SET) == 0 || ATTRCMP(term->line[y][x], term->c.attr)) {
2397 term->line[y][x] = term->c.attr;
2398 if (rev) term->line[y][x].mode ^= ATTR_REVERSE;
2399 if (gfx) term->line[y][x].mode |= ATTR_GFX;
2400 } else {
2401 int clen = utf8size(c), olen = utf8size(term->line[y][x].c);
2403 if (clen < 1 || (clen == olen && memcmp(c, term->line[y][x].c, clen) == 0)) return;
2406 markDirty(y, 1);
2408 term->line[y][x] = term->c.attr;
2409 if (rev) term->line[y][x].mode ^= ATTR_REVERSE;
2410 if (gfx) {
2411 term->line[y][x].mode &= ~(ATTR_GFX|ATTR_GFX1);
2412 term->line[y][x].mode |= (term->line[y][x].mode&ATTR_G1) ? ATTR_GFX1 : ATTR_GFX;
2415 term->line[y][x].state |= (GLYPH_SET | GLYPH_DIRTY);
2416 memmove(term->line[y][x].c, c, UTF_SIZ);
2418 if (IS_GFX(term->line[y][x].mode)) {
2419 unsigned char c = (unsigned char)(term->line[y][x].c[0]);
2421 if (c > 95 && c < 128) term->line[y][x].c[0] -= 95;
2422 else if (c > 127) term->line[y][x].c[0] = ' ';
2424 if (term->sel.clip != NULL && selected(x, y)) {
2425 selclear(NULL);
2426 xfixsel();
2428 //dlogf("tsetchar(%d,%d): [%c] (%d); dirty=%d\n", x, y, c[0], term->wantRedraw, term->dirty[y]);
2432 static void tdeletechar (int n) {
2433 int src = term->c.x+n;
2434 int dst = term->c.x;
2435 int size = term->col-src;
2437 markDirty(term->c.y, 2);
2438 if (src >= term->col) {
2439 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2440 } else {
2441 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2442 tclearregion(term->col-n, term->c.y, term->col-1, term->c.y);
2447 static void tinsertblank (int n) {
2448 int src = term->c.x;
2449 int dst = src+n;
2450 int size = term->col-dst;
2452 markDirty(term->c.y, 2);
2453 if (dst >= term->col) {
2454 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2455 } else {
2456 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2457 tclearregion(src, term->c.y, dst-1, term->c.y);
2462 static void tinsertblankline (int n) {
2463 if (term->c.y < term->top || term->c.y > term->bot) return;
2464 tscrolldown(term->c.y, n);
2468 static void tdeleteline (int n) {
2469 if (term->c.y < term->top || term->c.y > term->bot) return;
2470 tscrollup(term->c.y, n);
2474 static void tsetattr (int *attr, int l) {
2475 for (int f = 0; f < l; ++f) {
2476 switch (attr[f]) {
2477 case 0:
2478 term->c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
2479 term->c.attr.mode |= ATTR_DEFFG | ATTR_DEFBG;
2480 term->c.attr.fg = term->deffg;
2481 term->c.attr.bg = term->defbg;
2482 break;
2483 case 1:
2484 term->c.attr.mode |= ATTR_BOLD;
2485 break;
2486 case 4:
2487 term->c.attr.mode |= ATTR_UNDERLINE;
2488 break;
2489 case 7:
2490 term->c.attr.mode |= ATTR_REVERSE;
2491 break;
2492 case 22:
2493 term->c.attr.mode &= ~ATTR_BOLD;
2494 break;
2495 case 24:
2496 term->c.attr.mode &= ~ATTR_UNDERLINE;
2497 break;
2498 case 27:
2499 term->c.attr.mode &= ~ATTR_REVERSE;
2500 break;
2501 case 38:
2502 if (f+2 < l && attr[f+1] == 5) {
2503 f += 2;
2504 if (BETWEEN(attr[f], 0, 255)) {
2505 term->c.attr.fg = attr[f];
2506 term->c.attr.mode &= ~ATTR_DEFFG;
2507 } else {
2508 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[f]);
2510 } else {
2511 //fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2512 term->c.attr.fg = term->deffg;
2513 term->c.attr.mode |= ATTR_DEFFG;
2515 break;
2516 case 39:
2517 term->c.attr.fg = term->deffg;
2518 term->c.attr.mode |= ATTR_DEFFG;
2519 break;
2520 case 48:
2521 if (f+2 < l && attr[f+1] == 5) {
2522 f += 2;
2523 if (BETWEEN(attr[f], 0, 255)) {
2524 term->c.attr.bg = attr[f];
2525 term->c.attr.mode &= ~ATTR_DEFBG;
2526 } else {
2527 fprintf(stderr, "erresc: bad bgcolor %d\n", attr[f]);
2529 } else {
2530 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2532 break;
2533 case 49:
2534 term->c.attr.bg = term->defbg;
2535 term->c.attr.mode |= ATTR_DEFBG;
2536 break;
2537 default:
2538 if (BETWEEN(attr[f], 30, 37)) { term->c.attr.fg = attr[f]-30; term->c.attr.mode &= ~ATTR_DEFFG; }
2539 else if (BETWEEN(attr[f], 40, 47)) { term->c.attr.bg = attr[f]-40; term->c.attr.mode &= ~ATTR_DEFBG; }
2540 else if (BETWEEN(attr[f], 90, 97)) { term->c.attr.fg = attr[f]-90+8; term->c.attr.mode &= ~ATTR_DEFFG; }
2541 else if (BETWEEN(attr[f], 100, 107)) { term->c.attr.fg = attr[f]-100+8; term->c.attr.mode &= ~ATTR_DEFFG; }
2542 else { fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]); csidump(); }
2543 break;
2549 static void tsetscroll (int t, int b) {
2550 int temp;
2552 LIMIT(t, 0, term->row-1);
2553 LIMIT(b, 0, term->row-1);
2554 if (t > b) {
2555 temp = t;
2556 t = b;
2557 b = temp;
2559 term->top = t;
2560 term->bot = b;
2564 ////////////////////////////////////////////////////////////////////////////////
2565 // esc processing
2566 static void csihandle (void) {
2567 switch (term->escseq.mode) {
2568 case '@': /* ICH -- Insert <n> blank char */
2569 DEFAULT(term->escseq.arg[0], 1);
2570 tinsertblank(term->escseq.arg[0]);
2571 break;
2572 case 'A': /* CUU -- Cursor <n> Up */
2573 case 'e':
2574 DEFAULT(term->escseq.arg[0], 1);
2575 tmoveto(term->c.x, term->c.y-term->escseq.arg[0]);
2576 break;
2577 case 'B': /* CUD -- Cursor <n> Down */
2578 DEFAULT(term->escseq.arg[0], 1);
2579 tmoveto(term->c.x, term->c.y+term->escseq.arg[0]);
2580 break;
2581 case 'C': /* CUF -- Cursor <n> Forward */
2582 case 'a':
2583 DEFAULT(term->escseq.arg[0], 1);
2584 tmoveto(term->c.x+term->escseq.arg[0], term->c.y);
2585 break;
2586 case 'D': /* CUB -- Cursor <n> Backward */
2587 DEFAULT(term->escseq.arg[0], 1);
2588 tmoveto(term->c.x-term->escseq.arg[0], term->c.y);
2589 break;
2590 case 'E': /* CNL -- Cursor <n> Down and first col */
2591 DEFAULT(term->escseq.arg[0], 1);
2592 tmoveto(0, term->c.y+term->escseq.arg[0]);
2593 break;
2594 case 'F': /* CPL -- Cursor <n> Up and first col */
2595 DEFAULT(term->escseq.arg[0], 1);
2596 tmoveto(0, term->c.y-term->escseq.arg[0]);
2597 break;
2598 case 'G': /* CHA -- Move to <col> */
2599 case '`': /* XXX: HPA -- same? */
2600 DEFAULT(term->escseq.arg[0], 1);
2601 tmoveto(term->escseq.arg[0]-1, term->c.y);
2602 break;
2603 case 'H': /* CUP -- Move to <row> <col> */
2604 case 'f': /* XXX: HVP -- same? */
2605 DEFAULT(term->escseq.arg[0], 1);
2606 DEFAULT(term->escseq.arg[1], 1);
2607 tmoveto(term->escseq.arg[1]-1, term->escseq.arg[0]-1);
2608 break;
2609 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
2610 case 'J': /* ED -- Clear screen */
2611 term->sel.bx = -1;
2612 switch (term->escseq.arg[0]) {
2613 case 0: /* below */
2614 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2615 if (term->c.y < term->row-1) tclearregion(0, term->c.y+1, term->col-1, term->row-1);
2616 break;
2617 case 1: /* above */
2618 if (term->c.y > 1) tclearregion(0, 0, term->col-1, term->c.y-1);
2619 tclearregion(0, term->c.y, term->c.x, term->c.y);
2620 break;
2621 case 2: /* all */
2622 tclearregion(0, 0, term->col-1, term->row-1);
2623 break;
2624 default:
2625 goto unknown;
2627 break;
2628 case 'K': /* EL -- Clear line */
2629 switch (term->escseq.arg[0]) {
2630 case 0: /* right */
2631 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2632 break;
2633 case 1: /* left */
2634 tclearregion(0, term->c.y, term->c.x, term->c.y);
2635 break;
2636 case 2: /* all */
2637 tclearregion(0, term->c.y, term->col-1, term->c.y);
2638 break;
2640 break;
2641 case 'S': /* SU -- Scroll <n> line up */
2642 DEFAULT(term->escseq.arg[0], 1);
2643 tscrollup(term->top, term->escseq.arg[0]);
2644 break;
2645 case 'T': /* SD -- Scroll <n> line down */
2646 DEFAULT(term->escseq.arg[0], 1);
2647 tscrolldown(term->top, term->escseq.arg[0]);
2648 break;
2649 case 'L': /* IL -- Insert <n> blank lines */
2650 DEFAULT(term->escseq.arg[0], 1);
2651 tinsertblankline(term->escseq.arg[0]);
2652 break;
2653 case 'l': /* RM -- Reset Mode */
2654 if (term->escseq.priv) {
2655 switch (term->escseq.arg[0]) {
2656 case 1: // 1001 for xterm compatibility
2657 DUMP_KEYPAD_SWITCH("1", "OFF");
2658 term->mode &= ~MODE_APPKEYPAD;
2659 break;
2660 case 5: /* DECSCNM -- Remove reverse video */
2661 if (IS_SET(MODE_REVERSE)) {
2662 term->mode &= ~MODE_REVERSE;
2663 tfulldirt();
2665 break;
2666 case 7: /* autowrap off */
2667 term->mode &= ~MODE_WRAP;
2668 break;
2669 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
2670 break;
2671 case 20: /* non-standard code? */
2672 term->mode &= ~MODE_CRLF;
2673 break;
2674 case 25: /* hide cursor */
2675 term->c.state |= CURSOR_HIDE;
2676 break;
2677 case 1000: /* disable X11 xterm mouse reporting */
2678 term->mode &= ~MODE_MOUSEBTN;
2679 break;
2680 case 1002:
2681 term->mode &= ~MODE_MOUSEMOTION;
2682 break;
2683 case 1049: /* = 1047 and 1048 */
2684 case 47:
2685 case 1047:
2686 if (IS_SET(MODE_ALTSCREEN)) {
2687 tclearregion(0, 0, term->col-1, term->row-1);
2688 tswapscreen();
2690 if (term->escseq.arg[0] != 1049) break;
2691 case 1048:
2692 tcursor(CURSOR_LOAD);
2693 break;
2694 default:
2695 goto unknown;
2697 } else {
2698 switch (term->escseq.arg[0]) {
2699 case 4:
2700 term->mode &= ~MODE_INSERT;
2701 break;
2702 default:
2703 goto unknown;
2706 break;
2707 case 'M': /* DL -- Delete <n> lines */
2708 DEFAULT(term->escseq.arg[0], 1);
2709 tdeleteline(term->escseq.arg[0]);
2710 break;
2711 case 'X': /* ECH -- Erase <n> char */
2712 DEFAULT(term->escseq.arg[0], 1);
2713 tclearregion(term->c.x, term->c.y, term->c.x + term->escseq.arg[0], term->c.y);
2714 break;
2715 case 'P': /* DCH -- Delete <n> char */
2716 DEFAULT(term->escseq.arg[0], 1);
2717 tdeletechar(term->escseq.arg[0]);
2718 break;
2719 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
2720 case 'd': /* VPA -- Move to <row> */
2721 DEFAULT(term->escseq.arg[0], 1);
2722 tmoveto(term->c.x, term->escseq.arg[0]-1);
2723 break;
2724 case 'h': /* SM -- Set terminal mode */
2725 if (term->escseq.priv) {
2726 switch (term->escseq.arg[0]) {
2727 case 1:
2728 DUMP_KEYPAD_SWITCH("1", "ON");
2729 term->mode |= MODE_APPKEYPAD;
2730 break;
2731 case 5: /* DECSCNM -- Reverve video */
2732 if (!IS_SET(MODE_REVERSE)) {
2733 term->mode |= MODE_REVERSE;
2734 tfulldirt();
2736 break;
2737 case 7:
2738 term->mode |= MODE_WRAP;
2739 break;
2740 case 20:
2741 term->mode |= MODE_CRLF;
2742 break;
2743 case 12: /* att610 -- Start blinking cursor (IGNORED) */
2744 /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
2745 if (term->escseq.narg > 1 && term->escseq.arg[1] != 25) break;
2746 case 25:
2747 term->c.state &= ~CURSOR_HIDE;
2748 break;
2749 case 1000: /* 1000,1002: enable xterm mouse report */
2750 term->mode |= MODE_MOUSEBTN;
2751 break;
2752 case 1002:
2753 term->mode |= MODE_MOUSEMOTION;
2754 break;
2755 case 1049: /* = 1047 and 1048 */
2756 case 47:
2757 case 1047:
2758 if (IS_SET(MODE_ALTSCREEN)) tclearregion(0, 0, term->col-1, term->row-1); else tswapscreen();
2759 if (term->escseq.arg[0] != 1049) break;
2760 case 1048:
2761 tcursor(CURSOR_SAVE);
2762 break;
2763 default: goto unknown;
2765 } else {
2766 switch (term->escseq.arg[0]) {
2767 case 4:
2768 term->mode |= MODE_INSERT;
2769 break;
2770 default:
2771 goto unknown;
2774 break;
2775 case 'm': /* SGR -- Terminal attribute (color) */
2776 tsetattr(term->escseq.arg, term->escseq.narg);
2777 break;
2778 case 'r': /* DECSTBM -- Set Scrolling Region */
2779 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
2780 // xterm compatibility
2781 DUMP_KEYPAD_SWITCH("1001", "OFF");
2782 term->mode &= ~MODE_APPKEYPAD;
2783 } else if (term->escseq.priv) {
2784 goto unknown;
2785 } else {
2786 DEFAULT(term->escseq.arg[0], 1);
2787 DEFAULT(term->escseq.arg[1], term->row);
2788 tsetscroll(term->escseq.arg[0]-1, term->escseq.arg[1]-1);
2789 tmoveto(0, 0);
2791 break;
2792 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
2793 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
2794 // xterm compatibility
2795 DUMP_KEYPAD_SWITCH("1001", "ON");
2796 term->mode |= MODE_APPKEYPAD;
2797 } else {
2798 tcursor(CURSOR_SAVE);
2800 break;
2801 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
2802 tcursor(CURSOR_LOAD);
2803 break;
2804 default:
2805 unknown:
2806 fprintf(stderr, "erresc: unknown csi ");
2807 csidump();
2808 break;
2813 static void csireset (void) {
2814 memset(&term->escseq, 0, sizeof(term->escseq));
2818 static void tputtab (void) {
2819 int space = opt_tabsize-term->c.x%opt_tabsize;
2821 if (space > 0) tmoveto(term->c.x+space, term->c.y);
2825 ////////////////////////////////////////////////////////////////////////////////
2826 // put char to output buffer or process command
2827 static void tputc (const char *c) {
2828 char ascii = *c;
2830 //dlogf("tputc: [%c]\n", c[0]);
2831 if (term->esc & ESC_START) {
2832 if (term->esc & ESC_CSI) {
2833 term->escseq.buf[term->escseq.len++] = ascii;
2834 if (BETWEEN(ascii, 0x40, 0x7E) || term->escseq.len >= ESC_BUF_SIZ) {
2835 term->esc = 0;
2836 csiparse();
2837 csihandle();
2839 } else if (term->esc & ESC_OSC) {
2840 /* TODO: handle other OSC */
2841 if (ascii == ';') {
2842 term->title[0] = 0;
2843 term->titlelen = 0;
2844 term->esc = ESC_START | ESC_TITLE;
2845 //updateTabBar = 1;
2847 } else if (term->esc & ESC_TITLE) {
2848 int len = utf8size(c);
2850 if (ascii == '\a' || term->titlelen+len >= ESC_TITLE_SIZ) {
2851 term->esc = 0;
2852 term->title[term->titlelen] = '\0';
2853 fixWindowTitle(term);
2854 updateTabBar = 1;
2855 } else if (len > 0) {
2856 memcpy(term->title+term->titlelen, c, len);
2857 term->titlelen += len;
2858 term->title[term->titlelen] = '\0';
2860 } else if (term->esc & ESC_ALTCHARSET) {
2861 term->esc = 0;
2862 switch (ascii) {
2863 case '0': /* Line drawing crap */
2864 term->c.attr.mode |= ATTR_GFX;
2865 break;
2866 case 'B': /* Back to regular text */
2867 term->c.attr.mode &= ~ATTR_GFX;
2868 break;
2869 default:
2870 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2871 term->c.attr.mode &= ~ATTR_GFX;
2872 break;
2874 } else if (term->esc & ESC_ALTG1) {
2875 term->esc = 0;
2876 switch (ascii) {
2877 case '0': /* Line drawing crap */
2878 term->c.attr.mode |= ATTR_GFX1;
2879 break;
2880 case 'B': /* Back to regular text */
2881 term->c.attr.mode &= ~ATTR_GFX1;
2882 break;
2883 default:
2884 fprintf(stderr, "esc unhandled charset: ESC ) %c\n", ascii);
2885 term->c.attr.mode &= ~ATTR_GFX1;
2886 break;
2888 } else if (term->esc & ESC_HASH) {
2889 term->esc = 0;
2890 switch (ascii) {
2891 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
2892 //tfillscreenwithE();
2893 break;
2895 } else if (term->esc & ESC_PERCENT) {
2896 term->esc = 0;
2897 } else {
2898 switch (ascii) {
2899 case '[': term->esc |= ESC_CSI; break;
2900 case ']': term->esc |= ESC_OSC; break;
2901 case '(': term->esc |= ESC_ALTCHARSET; break;
2902 case ')': term->esc |= ESC_ALTG1; break;
2903 case '#': term->esc |= ESC_HASH; break;
2904 case '%': term->esc |= ESC_PERCENT; break;
2905 case 'D': /* IND -- Linefeed */
2906 term->esc = 0;
2907 if (term->c.y == term->bot) tscrollup(term->top, 1); else tmoveto(term->c.x, term->c.y+1);
2908 break;
2909 case 'E': /* NEL -- Next line */
2910 term->esc = 0;
2911 tnewline(1); /* always go to first col */
2912 break;
2913 case 'M': /* RI -- Reverse linefeed */
2914 term->esc = 0;
2915 if (term->c.y == term->top) tscrolldown(term->top, 1); else tmoveto(term->c.x, term->c.y-1);
2916 break;
2917 case 'c': /* RIS -- Reset to inital state */
2918 term->esc = 0;
2919 treset();
2920 break;
2921 case '=': /* DECPAM -- Application keypad */
2922 DUMP_KEYPAD_SWITCH("=", "ON");
2923 term->esc = 0;
2924 term->mode |= MODE_APPKEYPAD;
2925 break;
2926 case '>': /* DECPNM -- Normal keypad */
2927 DUMP_KEYPAD_SWITCH(">", "OFF");
2928 term->esc = 0;
2929 term->mode &= ~MODE_APPKEYPAD;
2930 break;
2931 case '7': /* DECSC -- Save Cursor */
2932 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
2933 //TODO?
2934 term->esc = 0;
2935 tcursor(CURSOR_SAVE);
2936 break;
2937 case '8': /* DECRC -- Restore Cursor */
2938 //TODO?
2939 term->esc = 0;
2940 tcursor(CURSOR_LOAD);
2941 break;
2942 case 'Z': /* DEC private identification */
2943 term->esc = 0;
2944 ttywritestr("\x1b[?1;2c");
2945 break;
2946 default:
2947 term->esc = 0;
2948 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X ('%c')\n", (uchar)ascii, isprint(ascii)?ascii:'.');
2949 break;
2952 } else {
2953 if (term->sel.bx != -1 && BETWEEN(term->c.y, term->sel.by, term->sel.ey)) term->sel.bx = -1;
2954 switch (ascii) {
2955 case '\t': tputtab(); break;
2956 case '\b': tmoveto(term->c.x-1, term->c.y); break;
2957 case '\r': tmoveto(0, term->c.y); break;
2958 case '\v': tnewline(0); break;
2959 case '\f': case '\n': tnewline(IS_SET(MODE_CRLF)); break; /* go to first col if the mode is set */
2960 case '\a': if (!(xw.state & WIN_FOCUSED)) xseturgency(1); XBell(xw.dpy, 100); break;
2961 case 14: term->c.attr.mode |= ATTR_G1; break;
2962 case 15: term->c.attr.mode &= ~ATTR_G1; break;
2963 case '\033': csireset(); term->esc = ESC_START; break;
2964 default:
2965 if (term->needConv && IS_GFX(term->c.attr.mode)) {
2966 long cc;
2968 utf8decode(c, &cc);
2969 if (cc < 32 || cc >= 127) break; //FIXME: nothing at all
2970 } else {
2971 if ((unsigned char)ascii < 32 || ascii == 127) break; // seems that this chars are empty too
2973 if (IS_SET(MODE_WRAP) && (term->c.state&CURSOR_WRAPNEXT)) tnewline(1); /* always go to first col */
2974 tsetchar(c);
2975 if (term->c.x+1 < term->col) tmoveto(term->c.x+1, term->c.y); else term->c.state |= CURSOR_WRAPNEXT;
2976 break;
2982 static void tunshowhistory (void) {
2983 if (term != NULL && term->topline != 0) {
2984 term->topline = 0;
2985 term->wantRedraw = 1;
2986 term->lastDrawTime = 0;
2991 ////////////////////////////////////////////////////////////////////////////////
2992 // tty resising
2993 static int tresize (int col, int row) {
2994 int mincol = MIN(col, term->col);
2995 int slide = term->c.y-row+1;
2996 Glyph g;
2998 if (col < 1 || row < 1) return 0;
3000 g.state = GLYPH_DIRTY;
3001 g.mode = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG;
3002 g.fg = term->deffg;
3003 g.bg = term->defbg;
3004 g.c[0] = ' ';
3005 g.c[1] = 0;
3007 if (slide > 0) {
3008 tsetscroll(0, term->row-1);
3009 for (; slide > 0; --slide) tscrollup(0, 1); // to fill history
3012 if (row < term->row) {
3013 /* free unneeded rows */
3014 for (int f = row; f < term->row; ++f) free(term->alt[f]);
3015 for (int f = term->linecount-(term->row-row); f < term->linecount; ++f) free(term->line[f]);
3016 term->linecount -= (term->row-row);
3017 /* resize to new height */
3018 term->alt = realloc(term->alt, row*sizeof(Line));
3019 term->line = realloc(term->line, term->linecount*sizeof(Line));
3020 } else if (row > term->row) {
3021 /* resize to new height */
3022 term->alt = realloc(term->alt, row*sizeof(Line));
3023 term->line = realloc(term->line, (row+term->maxhistory)*sizeof(Line));
3024 /* add more lines */
3025 for (int f = term->row; f < row; ++f) {
3026 term->alt[f] = calloc(col, sizeof(Glyph));
3027 for (int x = 0; x < col; ++x) term->alt[f][x] = g;
3029 for (int f = 0; f < row-term->row; ++f) {
3030 int y = term->linecount++;
3032 term->line[y] = calloc(col, sizeof(Glyph));
3033 for (int x = 0; x < col; ++x) term->line[y][x] = g;
3037 if (row != term->row) {
3038 term->dirty = realloc(term->dirty, row*sizeof(*term->dirty));
3041 /* resize each row to new width, zero-pad if needed */
3042 for (int f = 0; f < term->linecount; ++f) {
3043 term->line[f] = realloc(term->line[f], col*sizeof(Glyph));
3044 for (int x = mincol; x < col; ++x) term->line[f][x] = g;
3045 if (f < row) {
3046 markDirty(f, 2);
3047 term->alt[f] = realloc(term->alt[f], col*sizeof(Glyph));
3048 for (int x = mincol; x < col; ++x) term->alt[f][x] = g;
3051 /* update terminal size */
3052 term->topline = 0;
3053 term->col = col;
3054 term->row = row;
3055 /* make use of the LIMIT in tmoveto */
3056 tmoveto(term->c.x, term->c.y);
3057 /* reset scrolling region */
3058 tsetscroll(0, row-1);
3059 tfulldirt();
3060 return (slide > 0);
3064 static void xresize (int col, int row) {
3065 Pixmap newbuf;
3066 int oldw, oldh;
3068 if (term == NULL) return;
3069 oldw = term->picbufw;
3070 oldh = term->picbufh;
3071 term->picbufw = MAX(1, col*xw.cw);
3072 term->picbufh = MAX(1, row*xw.ch);
3073 newbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3074 XCopyArea(xw.dpy, term->picbuf, newbuf, dc.gc, 0, 0, term->picbufw, term->picbufh, 0, 0);
3075 XFreePixmap(xw.dpy, term->picbuf);
3076 XSetForeground(xw.dpy, dc.gc, dc.col[term->defbg]);
3077 if (term->picbufw > oldw) {
3078 XFillRectangle(xw.dpy, newbuf, dc.gc, oldw, 0, term->picbufw-oldw, MIN(term->picbufh, oldh));
3079 } else if (term->picbufw < oldw && xw.w > term->picbufw) {
3080 XClearArea(xw.dpy, xw.win, term->picbufw, 0, xw.w-term->picbufh, MIN(term->picbufh, oldh), False);
3082 if (term->picbufh > oldh) {
3083 XFillRectangle(xw.dpy, newbuf, dc.gc, 0, oldh, term->picbufw, term->picbufh-oldh);
3084 } else if (term->picbufh < oldh && xw.h > term->picbufh) {
3085 XClearArea(xw.dpy, xw.win, 0, term->picbufh, xw.w, xw.h-term->picbufh, False);
3087 term->picbuf = newbuf;
3088 tfulldirt();
3089 updateTabBar = 1;
3093 ////////////////////////////////////////////////////////////////////////////////
3094 // x11 drawing and utils
3095 static void xloadcols (void) {
3096 int f, r, g, b;
3097 XColor color;
3098 ulong white = WhitePixel(xw.dpy, xw.scr);
3100 dc.col = calloc(MAX_COLOR+1, sizeof(dc.col[0]));
3101 for (f = 0; f <= MAX_COLOR; ++f) dc.col[f] = white;
3102 /* load colors [0-15] */
3103 for (f = 0; f <= 15; ++f) {
3104 const char *cname = opt_colornames[f]!=NULL?opt_colornames[f]:defcolornames[f];
3106 if (!XAllocNamedColor(xw.dpy, xw.cmap, cname, &color, &color)) {
3107 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, cname);
3108 } else {
3109 dc.col[f] = color.pixel;
3112 /* load colors [256-...] */
3113 for (f = 256; f <= MAX_COLOR; ++f) {
3114 const char *cname = opt_colornames[f];
3116 if (cname == NULL) {
3117 if (LEN(defextcolornames) <= f-256) continue;
3118 cname = defextcolornames[f-256];
3120 if (cname == NULL) continue;
3121 if (!XAllocNamedColor(xw.dpy, xw.cmap, cname, &color, &color)) {
3122 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, cname);
3123 } else {
3124 dc.col[f] = color.pixel;
3127 /* load colors [16-255] ; same colors as xterm */
3128 for (f = 16, r = 0; r < 6; ++r) {
3129 for (g = 0; g < 6; ++g) {
3130 for (b = 0; b < 6; ++b) {
3131 if (opt_colornames[f] != NULL) {
3132 if (!XAllocNamedColor(xw.dpy, xw.cmap, opt_colornames[f], &color, &color)) {
3133 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, opt_colornames[f]);
3134 } else {
3135 dc.col[f] = color.pixel;
3137 } else {
3138 color.red = r == 0 ? 0 : 0x3737+0x2828*r;
3139 color.green = g == 0 ? 0 : 0x3737+0x2828*g;
3140 color.blue = b == 0 ? 0 : 0x3737+0x2828*b;
3141 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3142 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3143 } else {
3144 dc.col[f] = color.pixel;
3147 ++f;
3151 for (r = 0; r < 24; ++r, ++f) {
3152 if (opt_colornames[f] != NULL) {
3153 if (!XAllocNamedColor(xw.dpy, xw.cmap, opt_colornames[f], &color, &color)) {
3154 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, opt_colornames[f]);
3155 } else {
3156 dc.col[f] = color.pixel;
3158 } else {
3159 color.red = color.green = color.blue = 0x0808+0x0a0a*r;
3160 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3161 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3162 } else {
3163 dc.col[f] = color.pixel;
3168 for (int f = 0; f < LEN(opt_colornames); ++f) if (opt_colornames[f]) free(opt_colornames[f]);
3172 static void xclear (int x1, int y1, int x2, int y2) {
3173 XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? term->deffg : term->defbg]);
3174 XFillRectangle(xw.dpy, term->picbuf, dc.gc, x1*xw.cw, y1*xw.ch, (x2-x1+1)*xw.cw, (y2-y1+1)*xw.ch);
3178 static void xhints (void) {
3179 XClassHint class = {opt_class, opt_title};
3180 XWMHints wm = {.flags = InputHint, .input = 1};
3181 XSizeHints size = {
3182 .flags = PSize | PResizeInc | PBaseSize,
3183 .height = xw.h,
3184 .width = xw.w,
3185 .height_inc = xw.ch,
3186 .width_inc = xw.cw,
3187 .base_height = xw.tabheight,
3188 .base_width = 0,
3190 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, &size, &wm, &class);
3194 static XFontSet xinitfont (const char *fontstr) {
3195 XFontSet set;
3196 char *def, **missing;
3197 int n;
3199 missing = NULL;
3200 set = XCreateFontSet(xw.dpy, fontstr, &missing, &n, &def);
3201 if (missing) {
3202 while (n--) fprintf(stderr, "sterm: missing fontset: %s\n", missing[n]);
3203 XFreeStringList(missing);
3205 return set;
3209 static void xgetfontinfo (XFontSet set, int *ascent, int *descent, short *lbearing, short *rbearing, Font *fid) {
3210 XFontStruct **xfonts;
3211 char **font_names;
3212 int n;
3214 *ascent = *descent = *lbearing = *rbearing = 0;
3215 n = XFontsOfFontSet(set, &xfonts, &font_names);
3216 for (int f = 0; f < n; ++f) {
3217 if (f == 0) *fid = (*xfonts)->fid;
3218 *ascent = MAX(*ascent, (*xfonts)->ascent);
3219 *descent = MAX(*descent, (*xfonts)->descent);
3220 *lbearing = MAX(*lbearing, (*xfonts)->min_bounds.lbearing);
3221 *rbearing = MAX(*rbearing, (*xfonts)->max_bounds.rbearing);
3222 ++xfonts;
3227 static void initfonts (const char *fontstr, const char *bfontstr, const char *tabfont) {
3228 if ((dc.font[0].set = xinitfont(fontstr)) == NULL) die("can't load font %s", fontstr);
3229 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);
3231 if ((dc.font[1].set = xinitfont(bfontstr)) == NULL) die("can't load font %s", bfontstr);
3232 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);
3234 if ((dc.font[2].set = xinitfont(tabfont)) == NULL) die("can't load font %s", tabfont);
3235 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);
3239 static void xinit (void) {
3240 XSetWindowAttributes attrs;
3241 Window parent;
3242 XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
3244 if (!(xw.dpy = XOpenDisplay(NULL))) die("can't open display");
3246 XA_VT_SELECTION = XInternAtom(xw.dpy, "_STERM_SELECTION_", 0);
3247 XA_CLIPBOARD = XInternAtom(xw.dpy, "CLIPBOARD", 0);
3248 XA_UTF8 = XInternAtom(xw.dpy, "UTF8_STRING", 0);
3249 XA_NETWM_NAME = XInternAtom(xw.dpy, "_NET_WM_NAME", 0);
3250 XA_TARGETS = XInternAtom(xw.dpy, "TARGETS", 0);
3251 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
3253 xw.scr = XDefaultScreen(xw.dpy);
3254 /* font */
3255 initfonts(opt_fontnorm, opt_fontbold, opt_fonttab);
3256 /* XXX: Assuming same size for bold font */
3257 xw.cw = dc.font[0].rbearing-dc.font[0].lbearing;
3258 xw.ch = dc.font[0].ascent+dc.font[0].descent;
3259 xw.tch = dc.font[2].ascent+dc.font[2].descent;
3260 xw.tabheight = xw.tch+2;
3261 //fprintf(stderr, "tabh: %d\n", xw.tabheight);
3262 //xw.tabheight = 0;
3263 /* colors */
3264 xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
3265 xloadcols();
3266 /* window - default size */
3267 term->picbufh = term->row*xw.ch;
3268 term->picbufw = term->col*xw.cw;
3270 xw.h = term->picbufh+xw.tabheight;
3271 xw.w = term->picbufw;
3273 attrs.background_pixel = dc.col[defaultBG];
3274 attrs.border_pixel = dc.col[defaultBG];
3275 attrs.bit_gravity = NorthWestGravity;
3276 attrs.event_mask = FocusChangeMask | KeyPressMask
3277 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
3278 | /*ButtonMotionMask*/ PointerMotionMask | ButtonPressMask | ButtonReleaseMask
3279 | EnterWindowMask | LeaveWindowMask;
3280 attrs.colormap = xw.cmap;
3281 //fprintf(stderr, "oe: [%s]\n", opt_embed);
3282 parent = opt_embed ? strtol(opt_embed, NULL, 0) : XRootWindow(xw.dpy, xw.scr);
3283 xw.win = XCreateWindow(xw.dpy, parent, 0, 0,
3284 xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
3285 XDefaultVisual(xw.dpy, xw.scr),
3286 CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask
3287 | CWColormap,
3288 &attrs);
3289 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3290 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
3291 /* input methods */
3292 xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
3293 xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL);
3294 /* gc */
3295 dc.gc = XCreateGC(xw.dpy, xw.win, 0, NULL);
3296 /* white cursor, black outline */
3297 xw.cursor = XCreateFontCursor(xw.dpy, XC_xterm);
3298 XDefineCursor(xw.dpy, xw.win, xw.cursor);
3299 XRecolorCursor(xw.dpy, xw.cursor,
3300 &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff},
3301 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
3302 fixWindowTitle(term);
3303 //XStoreName(xw.dpy, xw.win, opt_title);
3305 XSetForeground(xw.dpy, dc.gc, 0);
3306 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
3307 if (xw.tabheight > 0) XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
3309 XMapWindow(xw.dpy, xw.win);
3310 xhints();
3312 #if BLANKPTR_USE_GLYPH_CURSOR
3313 xw.blankPtr = XCreateGlyphCursor(xw.dpy, dc.font[0].fid, dc.font[0].fid, ' ', ' ', &blackcolor, &blackcolor);
3314 #else
3315 static const char cmbmp[1] = {0};
3316 Pixmap pm;
3318 pm = XCreateBitmapFromData(xw.dpy, xw.win, cmbmp, 1, 1);
3319 xw.blankPtr = XCreatePixmapCursor(xw.dpy, pm, pm, &blackcolor, &blackcolor, 0, 0);
3320 XFreePixmap(xw.dpy, pm);
3321 #endif
3323 XSync(xw.dpy, 0);
3327 static void xblankPointer (void) {
3328 if (!ptrBlanked && xw.blankPtr != None) {
3329 ptrBlanked = 1;
3330 XDefineCursor(xw.dpy, xw.win, xw.blankPtr);
3331 XFlush(xw.dpy);
3336 static void xunblankPointer (void) {
3337 if (ptrBlanked && xw.cursor != None) {
3338 ptrBlanked = 0;
3339 XDefineCursor(xw.dpy, xw.win, xw.cursor);
3340 XFlush(xw.dpy);
3341 ptrLastMove = mclock_ticks();
3346 static void xdraws (const char *s, const Glyph *base, int x, int y, int charlen, int bytelen) {
3347 int fg = base->fg, bg = base->bg, temp;
3348 int winx = x*xw.cw, winy = y*xw.ch+dc.font[0].ascent, width = charlen*xw.cw;
3349 XFontSet fontset = dc.font[0].set;
3350 int defF = base->mode&ATTR_DEFFG, defB = base->mode&ATTR_DEFBG;
3352 /* only switch default fg/bg if term is in RV mode */
3353 if (IS_SET(MODE_REVERSE)) {
3354 if (defF) fg = term->defbg;
3355 if (defB) bg = term->deffg;
3357 if (base->mode&ATTR_REVERSE) defF = defB = 0;
3358 if (base->mode & ATTR_BOLD) {
3359 if (defF && defB && defaultBoldFG >= 0) fg = defaultBoldFG;
3360 else if (fg < 8) fg += 8;
3361 fontset = dc.font[1].set;
3363 if (base->mode & ATTR_UNDERLINE && defaultUnderlineFG >= 0) {
3364 if (defF && defB) fg = defaultUnderlineFG;
3367 if (base->mode&ATTR_REVERSE) { temp = fg; fg = bg; bg = temp; }
3369 XSetBackground(xw.dpy, dc.gc, dc.col[bg]);
3370 XSetForeground(xw.dpy, dc.gc, dc.col[fg]);
3374 FILE *fo = fopen("zlog.log", "ab");
3375 fprintf(fo, "xdraws: x=%d; y=%d; charlen=%d; bytelen=%d\n", x, y, charlen, bytelen);
3376 fclose(fo);
3380 if (IS_GFX(base->mode)) {
3381 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
3382 } else if (!needConversion) {
3383 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
3384 } else {
3385 if (bytelen > 0) {
3386 //k8: dunno why, but Xutf8DrawImageString() ignores ascii (at least for terminus)
3387 const char *pos = s;
3388 int xpos = winx;
3390 while (pos < s+bytelen) {
3391 const char *e;
3392 int clen;
3394 if ((unsigned char)(pos[0]) < 128) {
3395 for (e = pos+1; e < s+bytelen && (unsigned char)(*e) < 128; ++e) ;
3396 clen = e-pos;
3397 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
3400 FILE *fo = fopen("zlog.log", "ab");
3401 fprintf(fo, " norm: clen=%d (%d); [", clen, (int)(e-pos));
3402 fwrite(pos, 1, e-pos, fo);
3403 fprintf(fo, "]\n");
3404 fclose(fo);
3407 } else {
3408 for (clen = 0, e = pos; e < s+bytelen && (unsigned char)(*e) >= 128; ++e) {
3409 if (((unsigned char)(e[0])&0xc0) == 0xc0) ++clen;
3411 Xutf8DrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
3414 FILE *fo = fopen("zlog.log", "ab");
3415 fprintf(fo, " utf8: clen=%d (%d); [", clen, (int)(e-pos));
3416 fwrite(pos, 1, e-pos, fo);
3417 fprintf(fo, "]\n");
3418 fclose(fo);
3422 xpos += xw.cw*clen;
3423 pos = e;
3428 if (base->mode & ATTR_UNDERLINE) {
3429 XDrawLine(xw.dpy, term->picbuf, dc.gc, winx, winy+1, winx+width-1, winy+1);
3434 /* copy buffer pixmap to screen pixmap */
3435 static void xcopy (int x, int y, int cols, int rows) {
3436 int src_x = x*xw.cw, src_y = y*xw.ch, src_w = cols*xw.cw, src_h = rows*xw.ch;
3437 int dst_x = src_x, dst_y = src_y;
3439 if (opt_tabposition == 1) { dst_y += xw.tabheight; }
3440 XCopyArea(xw.dpy, term->picbuf, xw.win, dc.gc, src_x, src_y, src_w, src_h, dst_x, dst_y);
3444 static void xdrawcursor (void) {
3445 int sl;
3446 int scrx, scry;
3448 if (term == NULL) return;
3450 LIMIT(term->oldcx, 0, term->col-1);
3451 LIMIT(term->oldcy, 0, term->row-1);
3453 scrx = term->oldcx;
3454 scry = term->oldcy+term->topline;
3455 if (scry < term->row &&
3456 (term->oldcy != term->c.y || term->oldcx != term->c.x || (term->c.state&CURSOR_HIDE) || !(xw.state & WIN_FOCUSED))) {
3457 /* remove the old cursor */
3458 sl = utf8size(term->line[term->oldcy][scrx].c);
3459 xdraws(term->line[term->oldcy][scrx].c, &term->line[term->oldcy][scrx], scrx, scry, 1, sl);
3460 //xclear(scrx, term->oldcy, scrx, term->oldcy);
3461 xcopy(scrx, scry, 1, 1);
3463 /* draw the new one */
3464 if (!(term->c.state&CURSOR_HIDE)) {
3465 Glyph g;
3467 scrx = term->c.x;
3468 scry = term->c.y+term->topline;
3469 if (scry < term->row) {
3470 if (!(xw.state & WIN_FOCUSED)) {
3471 if (defaultCursorInactiveBG < 0) {
3472 XSetForeground(xw.dpy, dc.gc, dc.col[defaultCursorBG]);
3473 XDrawRectangle(xw.dpy, term->picbuf, dc.gc, scrx*xw.cw, scry*xw.ch, xw.cw-1, xw.ch-1);
3474 goto done;
3476 g.bg = defaultCursorInactiveBG;
3477 g.fg = defaultCursorInactiveFG;
3478 } else {
3479 g.fg = defaultCursorFG;
3480 g.bg = defaultCursorBG;
3482 memcpy(g.c, term->line[term->c.y][scrx].c, UTF_SIZ);
3483 g.state = 0;
3484 g.mode = 0;
3485 if (IS_SET(MODE_REVERSE)) g.mode |= ATTR_REVERSE;
3486 sl = utf8size(g.c);
3487 xdraws(g.c, &g, scrx, scry, 1, sl);
3488 term->oldcx = scrx;
3489 term->oldcy = term->c.y;
3491 done:
3492 xcopy(scrx, scry, 1, 1);
3497 static void xdrawTabBar (void) {
3498 if (xw.tabheight > 0 && updateTabBar) {
3499 static int tableft = 0;
3500 int tabstart;
3501 int tabw = xw.w/opt_tabcount;
3502 XFontSet fontset = dc.font[2].set;
3504 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabBG]);
3505 XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
3507 if (termidx < tableft) tableft = termidx;
3508 else if (termidx > tableft+opt_tabcount-1) tableft = termidx-opt_tabcount+1;
3509 if (tableft < 0) tableft = 0;
3511 tabstart = tableft;
3512 for (int f = tabstart; f < tabstart+opt_tabcount; ++f) {
3513 int x = (f-tabstart)*tabw;
3514 const char *title;
3515 char *tit;
3517 if (f >= term_count) {
3518 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabBG]);
3519 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, xw.w, xw.tabheight);
3521 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabFG]);
3522 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
3523 break;
3525 title = term_array[f]->title;
3526 if (!title[0]) title = opt_title;
3527 tit = SPrintf("[%d]%s", f, title);
3528 title = tit;
3530 XSetForeground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabBG : normalTabBG]);
3531 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, tabw, xw.tabheight);
3533 XSetBackground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabBG : normalTabBG]);
3534 XSetForeground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabFG : normalTabFG]);
3536 if (needConversion) {
3537 int xx = x+2;
3539 while (*title && xx < x+tabw) {
3540 const char *e = title;
3541 XRectangle r;
3543 memset(&r, 0, sizeof(r));
3545 if ((unsigned char)(*e) > 127) {
3546 while (*e && (unsigned char)(*e) > 127) ++e;
3547 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
3548 Xutf8TextExtents(fontset, title, e-title, &r, NULL);
3549 } else {
3550 while (*e && (unsigned char)(*e) <= 127) ++e;
3551 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
3552 XmbTextExtents(fontset, title, e-title, &r, NULL);
3554 title = e;
3555 xx += r.width-r.x;
3558 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
3559 } else {
3560 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
3562 free(tit);
3564 XSetForeground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabBG : normalTabBG]);
3565 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x+tabw-2, 0, 2, xw.tabheight);
3567 if (f > tabstart) {
3568 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabFG]);
3569 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
3573 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabFG]);
3574 //XDrawRectangle(xw.dpy, xw.pictab, dc.gc, -1, 0, xw.w+1, xw.tabheight+1);
3575 if (opt_tabposition == 0) {
3576 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, 0);
3577 } else {
3578 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, xw.tabheight-1, xw.w, xw.tabheight-1);
3581 if (opt_tabposition == 0) {
3582 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, xw.h-xw.tabheight);
3583 } else {
3584 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, 0);
3586 updateTabBar = 0;
3590 static void drawline (int x1, int x2, int scry, int lineno) {
3591 int ic, ib, ox, sl;
3592 int stx, ex;
3593 Glyph base, new;
3595 //dlogf("drawline: x1=%d; x2=%d; scry=%d; row:%d; lineno=%d\n", x1, x2, scry, term->row, lineno);
3596 if (scry < 0 || scry >= term->row) {
3597 return;
3599 if (lineno < 0 || lineno >= term->linecount) {
3600 xclear(0, scry, term->col-1, scry);
3601 xcopy(0, scry, term->col, 1);
3602 } else {
3603 if (lineno < term->row && term->topline == 0) {
3604 //if (term->topline != 0) term->dirty[lineno] = 2;
3605 if (!term->dirty[lineno]) return;
3606 // fix 'dirty' flag for line
3607 if (term->dirty[lineno]&0x02) {
3608 // mark full line as dirty
3609 for (int x = 0; x < term->col; ++x) term->line[lineno][x].state |= GLYPH_DIRTY;
3611 // correct 'dirty' flag
3612 term->dirty[lineno] = 0;
3613 if (x1 > 0) for (int x = 0; x < x1; ++x) if (term->line[lineno][x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
3614 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; }
3616 // find dirty region
3617 for (stx = x1; stx < x2; ++stx) if (term->line[lineno][stx].state&GLYPH_DIRTY) break;
3618 for (ex = x2; ex > stx; --ex) if (term->line[lineno][ex-1].state&GLYPH_DIRTY) break;
3619 if (stx >= x2 || ex <= stx) return; // nothing to do
3620 //dlogf(" region: (%d,%d)\n", stx, ex);
3621 } else {
3622 //if (lineno < term->row) term->dirty[lineno] = 0;
3623 stx = 0;
3624 ex = term->col;
3627 base = term->line[lineno][stx];
3628 ic = ib = 0;
3629 ox = stx;
3630 //xclear(stx, scry, ex-1, scry); //k8: actually, we don't need this for good monospace fonts, so...
3631 for (int x = stx; x < ex; ++x) {
3632 new = term->line[lineno][x];
3633 term->line[lineno][x].state &= ~GLYPH_DIRTY; //!
3634 if (term->sel.bx != -1 && new.c[0]) {
3635 if ((lineno < term->row && selected(x, lineno)) || (lineno >= term->row && selected(x, 0-(lineno-term->row+1)))) new.mode ^= ATTR_REVERSE;
3637 if (ib > 0 && (ATTRCMP(base, new) || ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
3638 // flush draw buffer
3639 xdraws(term->drawbuf, &base, ox, scry, ic, ib);
3640 ic = ib = 0;
3642 if (ib == 0) { ox = x; base = new; }
3643 sl = utf8size(new.c);
3644 memcpy(term->drawbuf+ib, new.c, sl);
3645 ib += sl;
3646 ++ic;
3648 if (ib > 0) xdraws(term->drawbuf, &base, ox, scry, ic, ib);
3649 //xcopy(0, scry, term->col, 1);
3650 if (term->c.y == lineno && term->c.x >= stx && term->c.x < ex) xdrawcursor();
3651 xcopy(stx, scry, ex-stx, 1);
3656 static void drawregion (int x1, int y1, int x2, int y2, int forced) {
3657 if (!forced && (xw.state&WIN_VISIBLE) == 0) {
3658 //dlogf("invisible");
3659 term->lastDrawTime = 1;
3660 term->wantRedraw = 1;
3661 return;
3664 if (term->topline < term->row) {
3665 for (int y = y1; y < y2; ++y) drawline(x1, x2, y+term->topline, y);
3667 if (term->topline > 0) {
3668 int scry = MIN(term->topline, term->row), y = term->row;
3670 if (term->topline >= term->row) y += term->topline-term->row;
3671 while (--scry >= 0) {
3672 drawline(0, term->col, scry, y);
3673 ++y;
3676 xdrawcursor();
3677 xdrawTabBar();
3678 //XFlush(xw.dpy);
3679 term->lastDrawTime = mclock_ticks();
3680 term->wantRedraw = 0;
3681 lastDrawTime = mclock_ticks();
3685 static void draw (int forced) {
3686 if (term != NULL) {
3687 //dlogf("draw(%d)\n", forced);
3688 drawregion(0, 0, term->col, term->row, forced);
3693 static void expose (XEvent *ev) {
3694 XExposeEvent *e = &ev->xexpose;
3696 if (xw.state&WIN_REDRAW) {
3697 if (!e->count && term != NULL) {
3698 xw.state &= ~WIN_REDRAW;
3699 xcopy(0, 0, term->col, term->row);
3701 } else if (term != NULL) {
3702 //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)));
3703 xcopy(0, 0, term->col, term->row);
3705 xdrawTabBar();
3706 //XFlush(xw.dpy);
3710 static void visibility (XEvent *ev) {
3711 XVisibilityEvent *e = &ev->xvisibility;
3713 if (e->state == VisibilityFullyObscured) xw.state &= ~WIN_VISIBLE;
3714 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 */
3718 static void unmap (XEvent *ev) {
3719 xw.state &= ~WIN_VISIBLE;
3723 static void xseturgency (int add) {
3724 XWMHints *h = XGetWMHints(xw.dpy, xw.win);
3726 h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
3727 XSetWMHints(xw.dpy, xw.win, h);
3728 XFree(h);
3732 static void focus (XEvent *ev) {
3733 if (ev->type == FocusIn) {
3734 xw.state |= WIN_FOCUSED;
3735 xseturgency(0);
3736 } else {
3737 xw.state &= ~WIN_FOCUSED;
3739 //draw(1);
3740 xdrawcursor();
3741 xdrawTabBar();
3742 xcopy(0, 0, term->col, term->row);
3746 ////////////////////////////////////////////////////////////////////////////////
3747 // keyboard mapping
3748 static const char *kmap (KeySym k, uint state) {
3749 const char *res = NULL;
3751 state &= ~Mod2Mask; // numlock
3752 for (int f = 0; f < keymap_used; ++f) {
3753 uint mask = keymap[f].mask;
3755 if (keymap[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) {
3756 //fprintf(stderr, "kp: %d\n", IS_SET(MODE_APPKEYPAD));
3757 if (!IS_SET(MODE_APPKEYPAD)) {
3758 if (!keymap[f].kp) {
3759 //fprintf(stderr, "nokp! %d %s\n", keymap[f].kp, keymap[f].str+1);
3760 return keymap[f].str; // non-keypad hit
3762 continue;
3764 //fprintf(stderr, "kp! %d %s\n", keymap[f].kp, keymap[f].str+1);
3765 if (keymap[f].kp) return keymap[f].str; // keypad hit
3766 res = keymap[f].str; // kp mode, but non-kp mapping found
3769 return res;
3773 static const char *kbind (KeySym k, uint state) {
3774 state &= ~Mod2Mask; // numlock
3775 for (int f = 0; f < keybinds_used; ++f) {
3776 uint mask = keybinds[f].mask;
3778 if (keybinds[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) return keybinds[f].str;
3780 return NULL;
3784 static KeySym do_keytrans (KeySym ks) {
3785 for (int f = 0; f < keytrans_used; ++f) if (keytrans[f].src == ks) return keytrans[f].dst;
3786 return ks;
3790 static void kpress (XEvent *ev) {
3791 XKeyEvent *e = &ev->xkey;
3792 KeySym ksym = NoSymbol;
3793 const char *kstr;
3794 int len;
3795 Status status;
3796 char buf[32];
3798 if (term == NULL) return;
3799 //if (len < 1) len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
3800 if ((len = Xutf8LookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status)) > 0) buf[len] = 0;
3801 // leave only known mods
3802 e->state &= (Mod1Mask | Mod4Mask | ControlMask | ShiftMask);
3803 #ifdef DUMP_KEYSYMS
3805 const char *ksname = XKeysymToString(ksym);
3807 fprintf(stderr, "utf(%d):[%s] (%s) 0x%08x\n", len, len>=0?buf:"<shit>", ksname, (unsigned int)e->state);
3809 #endif
3810 if ((kstr = kbind(ksym, e->state)) != NULL) {
3811 // keybind found
3812 executeCommands(kstr);
3813 return;
3816 if ((kstr = kmap(do_keytrans(ksym), e->state)) != NULL) {
3817 if (kstr[0]) {
3818 tunshowhistory();
3819 ttywritestr(kstr);
3821 } else {
3822 int meta = (e->state&Mod1Mask);
3824 int shift = (e->state&ShiftMask);
3825 int ctrl = (e->state&ControlMask);
3827 switch (ksym) {
3828 case XK_Return:
3829 tunshowhistory();
3830 if (meta) {
3831 ttywritestr("\x1b\x0a");
3832 } else {
3833 if (IS_SET(MODE_CRLF)) ttywritestr("\r\n"); else ttywritestr("\r");
3835 break;
3836 default:
3837 if (len > 0) {
3838 tunshowhistory();
3839 if (meta && len == 1) ttywritestr("\x1b");
3840 ttywrite(buf, len);
3842 break;
3848 ////////////////////////////////////////////////////////////////////////////////
3849 // xembed?
3850 static void cmessage (XEvent *e) {
3851 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
3852 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
3853 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
3854 xw.state |= WIN_FOCUSED;
3855 xseturgency(0);
3856 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
3857 xw.state &= ~WIN_FOCUSED;
3859 draw(1);
3864 ////////////////////////////////////////////////////////////////////////////////
3865 static void resize (XEvent *e) {
3866 int col, row;
3867 Term *ot = term;
3869 if (e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) return;
3870 xw.w = e->xconfigure.width;
3871 xw.h = e->xconfigure.height;
3872 col = xw.w/xw.cw;
3873 row = (xw.h-xw.tabheight)/xw.ch;
3874 if (col == term->col && row == term->row) return;
3875 for (int f = 0; f < term_count; ++f) {
3876 term = term_array[f];
3877 if (tresize(col, row) && ot == term) draw(1);
3878 ttyresize();
3879 xresize(col, row);
3881 term = ot;
3882 XFreePixmap(xw.dpy, xw.pictab);
3883 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
3884 updateTabBar = 1;
3888 static inline int last_draw_too_old (void) {
3889 int tt = mclock_ticks();
3891 if (term != NULL && !term->dead && term->wantRedraw && tt-term->lastDrawTime >= opt_drawtimeout) return 1;
3892 return (tt-lastDrawTime >= 500);
3896 ////////////////////////////////////////////////////////////////////////////////
3897 // main loop
3898 static void (*handler[LASTEvent])(XEvent *) = {
3899 [KeyPress] = kpress,
3900 [ClientMessage] = cmessage,
3901 [ConfigureNotify] = resize,
3902 [VisibilityNotify] = visibility,
3903 [UnmapNotify] = unmap,
3904 [Expose] = expose,
3905 [FocusIn] = focus,
3906 [FocusOut] = focus,
3907 [MotionNotify] = bmotion,
3908 [ButtonPress] = bpress,
3909 [ButtonRelease] = brelease,
3910 [SelectionNotify] = selnotify,
3911 [SelectionRequest] = selrequest,
3912 [SelectionClear] = selclear,
3916 static void run (void) {
3917 //int stuff_to_print = 0;
3918 int xfd = XConnectionNumber(xw.dpy);
3920 ptrLastMove = mclock_ticks();
3921 while (term_count > 0) {
3922 XEvent ev;
3923 fd_set rfd, wfd;
3924 struct timeval timeout;
3925 int maxfd = xfd;
3926 Term *ot;
3928 FD_ZERO(&rfd);
3929 FD_ZERO(&wfd);
3930 FD_SET(xfd, &rfd);
3931 //FD_SET(term->cmdfd, &rfd);
3932 // have something to write?
3933 for (int f = 0; f < term_count; ++f) {
3934 Term *t = term_array[f];
3936 if (!t->dead && term->cmdfd >= 0 && t->pid != 0) {
3937 if (t->cmdfd > maxfd) maxfd = t->cmdfd;
3938 FD_SET(t->cmdfd, &rfd);
3939 if (t->wrbufpos < t->wrbufused) FD_SET(t->cmdfd, &wfd);
3943 timeout.tv_sec = 0;
3944 timeout.tv_usec = (opt_drawtimeout+2)*1000;
3945 if (select(maxfd+1, &rfd, &wfd, NULL, &timeout) < 0) {
3946 if (errno == EINTR) continue;
3947 die("select failed: %s", SERRNO);
3950 ot = term;
3951 for (int f = 0; f < term_count; ++f) {
3952 Term *t = term_array[f];
3954 if (!t->dead && term->cmdfd >= 0 && term->pid != 0) {
3955 term = t;
3956 if (FD_ISSET(t->cmdfd, &wfd)) ttyflushwrbuf();
3957 if (FD_ISSET(t->cmdfd, &rfd)) ttyread(); //t->wantRedraw = 1;
3958 term = ot;
3962 termcleanup();
3963 if (term_count == 0) break;
3965 if (updateTabBar || last_draw_too_old()) {
3966 draw(0);
3969 if (XPending(xw.dpy)) {
3970 while (XPending(xw.dpy)) {
3971 XNextEvent(xw.dpy, &ev);
3972 switch (ev.type) {
3973 //case VisibilityNotify:
3974 //case UnmapNotify:
3975 //case FocusIn:
3976 //case FocusOut:
3977 case MotionNotify:
3978 case ButtonPress:
3979 case ButtonRelease:
3980 xunblankPointer();
3981 ptrLastMove = mclock_ticks();
3982 break;
3983 default: ;
3985 if (XFilterEvent(&ev, xw.win)) continue;
3986 if (handler[ev.type]) (handler[ev.type])(&ev);
3990 if (!ptrBlanked && opt_ptrblank > 0 && mclock_ticks()-ptrLastMove >= opt_ptrblank) {
3991 xblankPointer();
3997 ////////////////////////////////////////////////////////////////////////////////
3998 typedef const char * (*IniHandlerFn) (const char *optname, const char *fmt, char *argstr, void *udata);
4001 typedef struct {
4002 const char *name;
4003 const char *fmt;
4004 void *udata;
4005 IniHandlerFn fn;
4006 } IniCommand;
4008 static const char *inifnGenericOneArg (const char *optname, const char *fmt, char *argstr, void *udata) {
4009 return iniParseArguments(argstr, fmt, udata);
4013 static const char *inifnGenericOneStr (const char *optname, const char *fmt, char *argstr, void *udata) {
4014 char *s = NULL;
4015 const char *err = iniParseArguments(argstr, fmt, &s);
4017 if (err != NULL) return err;
4018 if ((s = strdup(s)) == NULL) return "out of memory";
4019 if (udata) {
4020 char **ustr = (char **)udata;
4022 if (*ustr) free(*ustr);
4023 *ustr = s;
4025 return NULL;
4029 static const IniCommand iniCommands[] = {
4030 {"term", "s!-", &opt_term, inifnGenericOneStr},
4031 {"class", "s!-", &opt_class, inifnGenericOneStr},
4032 {"title", "s!-", &opt_title, inifnGenericOneStr},
4033 {"fontnorm", "s!-", &opt_fontnorm, inifnGenericOneStr},
4034 {"fontbold", "s!-", &opt_fontbold, inifnGenericOneStr},
4035 {"fonttab", "s!-", &opt_fonttab, inifnGenericOneStr},
4036 {"shell", "s!-", &opt_shell, inifnGenericOneStr},
4037 {"doubleclick_timeout", "i{0,10000}", &opt_doubleclick_timeout, inifnGenericOneArg},
4038 {"tripleclick_timeout", "i{0,10000}", &opt_tripleclick_timeout, inifnGenericOneArg},
4039 {"tabsize", "i{1,256}", &opt_tabsize, inifnGenericOneArg},
4040 {"defaultfg", "i{0,511}", &defaultFG, inifnGenericOneArg},
4041 {"defaultbg", "i{0,511}", &defaultBG, inifnGenericOneArg},
4042 {"defaultcursorfg", "i{0,511}", &defaultCursorFG, inifnGenericOneArg},
4043 {"defaultcursorbg", "i{0,511}", &defaultCursorBG, inifnGenericOneArg},
4044 {"defaultinactivecursorfg", "i{0,511}", &defaultCursorInactiveFG, inifnGenericOneArg},
4045 {"defaultinactivecursorbg", "i{-1,511}", &defaultCursorInactiveBG, inifnGenericOneArg},
4046 {"defaultboldfg", "i{-1,511}", &defaultBoldFG, inifnGenericOneArg},
4047 {"defaultunderlinefg", "i{-1,511}", &defaultUnderlineFG, inifnGenericOneArg},
4048 {"normaltabfg", "i{0,511}", &normalTabFG, inifnGenericOneArg},
4049 {"normaltabbg", "i{0,511}", &normalTabBG, inifnGenericOneArg},
4050 {"activetabfg", "i{0,511}", &activeTabFG, inifnGenericOneArg},
4051 {"activetabbg", "i{0,511}", &activeTabBG, inifnGenericOneArg},
4052 {"maxhistory", "i{0,65535}", &opt_maxhistory, inifnGenericOneArg},
4053 {"ptrblank", "i{0,65535}", &opt_ptrblank, inifnGenericOneArg},
4054 {"tabcount", "i{1,128}", &opt_tabcount, inifnGenericOneArg},
4055 {"tabposition", "i{0,1}", &opt_tabposition, inifnGenericOneArg},
4056 {"draw_timeout", "i{5,30000}", &opt_drawtimeout, inifnGenericOneArg},
4057 {NULL, NULL, NULL, NULL}
4061 #define INI_LINE_SIZE (32768)
4063 // <0: file not found
4064 // >0: file loading error
4065 // 0: ok
4066 static int loadConfig (const char *fname) {
4067 int inifelse = 0; // 0: not; 1: doing true; 2: doing false; -1: waiting else/endif; -2: waiting endif
4068 FILE *fi = fopen(fname, "r");
4069 const char *err = NULL;
4070 char *line;
4071 int lineno = 0;
4073 if (fi == NULL) return -1;
4074 if ((line = malloc(INI_LINE_SIZE)) == NULL) { err = "out of memory"; goto quit; }
4076 while (fgets(line, INI_LINE_SIZE-1, fi) != NULL) {
4077 char *optname, *argstr;
4078 int goodoption = 0;
4080 ++lineno;
4081 line[INI_LINE_SIZE-1] = 0;
4082 // get option name
4083 for (optname = line; *optname && isspace(*optname); ++optname) ;
4084 if (!optname[0] || optname[0] == '#') continue; // comment
4085 if (!isalnum(optname[0])) { err = "invalid option name"; goto quit; }
4086 argstr = optname;
4087 while (*argstr) {
4088 if (!argstr[0] || isspace(argstr[0])) break;
4089 if (!isalnum(argstr[0]) && argstr[0] != '_' && argstr[0] != '.') { err = "invalid option name"; goto quit; }
4090 *argstr = tolower(*argstr);
4091 ++argstr;
4093 if (*argstr) *argstr++ = 0;
4095 if (strcasecmp(optname, "ifterm") == 0) {
4096 char *val;
4098 if (inifelse != 0) { err = "nested ifs are not allowed"; goto quit; }
4099 inifelse = -1;
4100 if ((err = iniParseArguments(argstr, "s", &val)) != NULL) goto quit;
4101 if (strcasecmp(opt_term, val) == 0) inifelse = 1;
4102 continue;
4104 if (strcasecmp(optname, "else") == 0) {
4105 switch (inifelse) {
4106 case -1: inifelse = 2; break;
4107 case 2: case -2: case 0: err = "else without if"; goto quit;
4108 case 1: inifelse = -2; break;
4110 continue;
4112 if (strcasecmp(optname, "endif") == 0) {
4113 switch (inifelse) {
4114 case -1: case -2: case 1: case 2: inifelse = 0; break;
4115 case 0: err = "endif without if"; goto quit;
4117 continue;
4120 if (inifelse < 0) {
4121 //trimstr(argstr);
4122 //fprintf(stderr, "skip: [%s]\n", argstr);
4123 continue;
4125 if (opt_term_locked && strcasecmp(optname, "term") == 0) continue; // termname given in command line
4126 // ok, we have option name in `optname` and arguments in `argstr`
4127 if (strncmp(optname, "color.", 6) == 0) {
4128 int n = 0;
4129 char *s = NULL;
4131 optname += 6;
4132 if (!optname[0]) { err = "invalid color option"; goto quit; }
4133 while (*optname) {
4134 if (!isdigit(*optname)) { err = "invalid color option"; goto quit; }
4135 n = (n*10)+(optname[0]-'0');
4136 ++optname;
4138 if (n < 0 || n > 511) { err = "invalid color index"; goto quit; }
4140 if ((err = iniParseArguments(argstr, "s!-", &s)) != NULL) goto quit;
4141 if ((s = strdup(s)) == NULL) { err = "out of memory"; goto quit; }
4142 if (opt_colornames[n] != NULL) free(opt_colornames[n]);
4143 opt_colornames[n] = s;
4144 continue;
4147 if (strcmp(optname, "unimap") == 0) {
4148 int uni, ch;
4149 char *alt = NULL;
4151 //unimap 0x2592 0x61 alt
4152 if ((err = iniParseArguments(argstr, "i{0,65535}i{0,126}|s!-", &uni, &ch, &alt)) != NULL) goto quit;
4153 if (alt != NULL && strcasecmp(alt, "alt") != 0) { err = "invalid unimap"; goto quit; }
4154 if (unimap == NULL) {
4155 if ((unimap = calloc(65536, sizeof(unimap[0]))) == NULL) { err = "out of memory"; goto quit; }
4157 if (alt != NULL && ch == 0) alt = NULL;
4158 if (alt != NULL && ch < 96) { err = "invalid unimap"; goto quit; }
4159 unimap[uni] = ch;
4160 if (alt != NULL) unimap[uni] |= 0x80;
4161 continue;
4164 if (strcmp(optname, "keytrans_reset") == 0) {
4165 if ((err = iniParseArguments(argstr, "")) != NULL) goto quit;
4166 keytrans_reset();
4167 continue;
4169 if (strcmp(optname, "keytrans") == 0) {
4170 char *src = NULL, *dst = NULL;
4172 if ((err = iniParseArguments(argstr, "s!-s!-", &src, &dst)) != NULL) goto quit;
4173 keytrans_add(src, dst);
4174 continue;
4177 if (strcmp(optname, "keybind_reset") == 0) {
4178 if ((err = iniParseArguments(argstr, "")) != NULL) goto quit;
4179 keybinds_reset();
4180 continue;
4182 if (strcmp(optname, "keybind") == 0) {
4183 char *key = NULL, *act = NULL;
4185 if ((err = iniParseArguments(argstr, "s!-R!", &key, &act)) != NULL) goto quit;
4186 keybind_add(key, act);
4187 continue;
4190 if (strcmp(optname, "keymap_reset") == 0) {
4191 if ((err = iniParseArguments(argstr, "")) != NULL) goto quit;
4192 keymap_reset();
4193 continue;
4195 if (strcmp(optname, "keymap") == 0) {
4196 char *key = NULL, *str = NULL;
4198 if ((err = iniParseArguments(argstr, "s!-s!-", &key, &str)) != NULL) goto quit;
4199 keymap_add(key, str);
4200 continue;
4203 for (int f = 0; iniCommands[f].name != NULL; ++f) {
4204 if (strcmp(iniCommands[f].name, optname) == 0) {
4205 if ((err = iniCommands[f].fn(optname, iniCommands[f].fmt, argstr, iniCommands[f].udata)) != NULL) goto quit;
4206 goodoption = 1;
4207 break;
4210 if (!goodoption) {
4211 fprintf(stderr, "ini error at line %d: unknown option '%s'!\n", lineno, optname);
4214 quit:
4215 if (line != NULL) free(line);
4216 fclose(fi);
4217 if (err == NULL && inifelse != 0) err = "if without endif";
4218 if (err != NULL) die("ini error at line %d: %s", lineno, err);
4219 return 0;
4223 static void initDefaultOptions (void) {
4224 opt_title = strdup("sterm");
4225 opt_class = strdup("sterm");
4226 opt_term = strdup(TNAME);
4227 opt_fontnorm = strdup(FONT);
4228 opt_fontbold = strdup(BOLDFONT);
4229 opt_fonttab = strdup(FONTTAB);
4230 opt_shell = strdup(SHELL);
4232 memset(opt_colornames, 0, sizeof(opt_colornames));
4233 for (int f = 0; f < LEN(defcolornames); ++f) opt_colornames[f] = strdup(defcolornames[f]);
4234 for (int f = 0; f < LEN(defextcolornames); ++f) opt_colornames[f+256] = strdup(defextcolornames[f]);
4236 keytrans_add("KP_Home", "Home");
4237 keytrans_add("KP_Left", "Left");
4238 keytrans_add("KP_Up", "Up");
4239 keytrans_add("KP_Right", "Right");
4240 keytrans_add("KP_Down", "Down");
4241 keytrans_add("KP_Prior", "Prior");
4242 keytrans_add("KP_Next", "Next");
4243 keytrans_add("KP_End", "End");
4244 keytrans_add("KP_Begin", "Begin");
4245 keytrans_add("KP_Insert", "Insert");
4246 keytrans_add("KP_Delete", "Delete");
4248 keybind_add("shift+Insert", "PastePrimary");
4249 keybind_add("alt+Insert", "PasteSecondary");
4251 keymap_add("BackSpace", "\177");
4252 keymap_add("Insert", "\x1b[2~");
4253 keymap_add("Delete", "\x1b[3~");
4254 keymap_add("Home", "\x1b[1~");
4255 keymap_add("End", "\x1b[4~");
4256 keymap_add("Prior", "\x1b[5~");
4257 keymap_add("Next", "\x1b[6~");
4258 keymap_add("F1", "\x1bOP");
4259 keymap_add("F2", "\x1bOQ");
4260 keymap_add("F3", "\x1bOR");
4261 keymap_add("F4", "\x1bOS");
4262 keymap_add("F5", "\x1b[15~");
4263 keymap_add("F6", "\x1b[17~");
4264 keymap_add("F7", "\x1b[18~");
4265 keymap_add("F8", "\x1b[19~");
4266 keymap_add("F9", "\x1b[20~");
4267 keymap_add("F10", "\x1b[21~");
4268 keymap_add("Up", "\x1bOA");
4269 keymap_add("Down", "\x1bOB");
4270 keymap_add("Right", "\x1bOC");
4271 keymap_add("Left", "\x1bOD");
4272 keymap_add("kpad+Up", "\x1bOA");
4273 keymap_add("kpad+Down", "\x1bOB");
4274 keymap_add("kpad+Right", "\x1bOC");
4275 keymap_add("kpad+Left", "\x1bOD");
4279 ////////////////////////////////////////////////////////////////////////////////
4280 static Term *oldTerm;
4281 static int oldTermIdx;
4282 static Term *newTerm;
4283 static int newTermIdx;
4284 static int newTermSwitch;
4287 typedef void (*CmdHandlerFn) (const char *cmdname, char *argstr);
4289 typedef struct {
4290 const char *name;
4291 CmdHandlerFn fn;
4292 } Command;
4295 static void cmdPastePrimary (const char *cmdname, char *argstr) {
4296 selpaste(XA_PRIMARY);
4300 static void cmdPasteSecondary (const char *cmdname, char *argstr) {
4301 selpaste(XA_SECONDARY);
4305 static void cmdPasteClipboard (const char *cmdname, char *argstr) {
4306 selpaste(XA_CLIPBOARD);
4310 static void cmdExec (const char *cmdname, char *argstr) {
4311 if (term->execcmd != NULL) free(term->execcmd);
4312 if (argstr[0]) {
4313 term->execcmd = strdup(argstr);
4314 } else {
4315 term->execcmd = NULL;
4320 static int parseTabArgs (char *argstr, int *noswitch, int nowrap, int idx) {
4321 for (;;) {
4322 char *arg;
4324 while (*argstr && isspace(*argstr)) ++argstr;
4325 if (!argstr[0]) break;
4326 if (iniParseArguments(argstr, "s-R-", &arg, &argstr) != NULL) break;
4328 if (strcasecmp(arg, "noswitch") == 0) *noswitch = 1;
4329 else if (strcasecmp(arg, "switch") == 0) *noswitch = 0;
4330 else if (strcasecmp(arg, "nowrap") == 0) nowrap = 1;
4331 else if (strcasecmp(arg, "wrap") == 0) nowrap = 0;
4332 else if (strcasecmp(arg, "first") == 0) idx = 0;
4333 else if (strcasecmp(arg, "last") == 0) idx = term_count-1;
4334 else if (strcasecmp(arg, "prev") == 0) idx = -1;
4335 else if (strcasecmp(arg, "next") == 0) idx = -2;
4336 else {
4337 long int n = -1;
4338 char *eptr;
4340 n = strtol(arg, &eptr, 0);
4341 if (!eptr[0] && n >= 0 && n < term_count) idx = n;
4344 switch (idx) {
4345 case -1: // prev
4346 if ((idx = termidx-1) < 0) idx = nowrap ? 0 : term_count-1;
4347 break;
4348 case -2: // next
4349 if ((idx = termidx+1) >= term_count) idx = nowrap ? term_count-1 : 0;
4350 break;
4352 return idx;
4356 static void flushNewTerm (void) {
4357 if (newTerm != NULL) {
4358 if (newTermSwitch && term != NULL) term->lastActiveTime = mclock_ticks();
4359 term = newTerm;
4360 termidx = newTermIdx;
4361 tinitialize(term_array[0]->col, term_array[0]->row);
4363 term->picbufh = term->row*xw.ch;
4364 term->picbufw = term->col*xw.cw;
4365 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
4366 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
4368 if (ttynew(term) != 0) {
4369 term = oldTerm;
4370 termidx = oldTermIdx;
4371 termfree(newTermIdx);
4372 } else {
4373 selinit();
4374 ttyresize();
4375 if (newTermSwitch) {
4376 term = NULL;
4377 termidx = 0;
4378 switchToTerm(newTermIdx, 1);
4379 oldTerm = term;
4380 oldTermIdx = termidx;
4381 } else {
4382 term = oldTerm;
4383 termidx = oldTermIdx;
4386 newTerm = NULL;
4391 static void cmdNewTab (const char *cmdname, char *argstr) {
4392 int noswitch = 0, idx;
4394 flushNewTerm();
4395 if ((newTerm = termalloc()) == NULL) return;
4396 /*idx =*/ parseTabArgs(argstr, &noswitch, 0, termidx);
4397 idx = term_count-1;
4398 if (!noswitch) {
4399 if (term != NULL) term->lastActiveTime = mclock_ticks();
4400 oldTermIdx = idx;
4402 newTermIdx = termidx = idx;
4403 term = newTerm;
4404 newTermSwitch = !noswitch;
4408 static void cmdCloseTab (const char *cmdname, char *argstr) {
4409 flushNewTerm();
4410 if (!term->dead) kill(term->pid, SIGTERM);
4414 static void cmdKillTab (const char *cmdname, char *argstr) {
4415 flushNewTerm();
4416 if (!term->dead) kill(term->pid, SIGKILL);
4420 static void cmdSwitchToTab (const char *cmdname, char *argstr) {
4421 int noswitch = 0, idx;
4423 flushNewTerm();
4424 idx = parseTabArgs(argstr, &noswitch, 0, -666);
4425 if (idx >= 0) {
4426 switchToTerm(idx, 1);
4427 oldTerm = term;
4428 oldTermIdx = termidx;
4433 static void cmdMoveTabTo (const char *cmdname, char *argstr) {
4434 int noswitch = 0, idx;
4436 flushNewTerm();
4437 idx = parseTabArgs(argstr, &noswitch, 0, termidx);
4438 if (idx != termidx && idx >= 0 && idx < term_count) {
4439 Term *t = term_array[termidx];
4441 // remove current term
4442 for (int f = termidx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
4443 // insert term
4444 for (int f = term_count-2; f >= idx; --f) term_array[f+1] = term_array[f];
4445 term_array[idx] = t;
4446 termidx = idx;
4447 oldTerm = t;
4448 oldTermIdx = idx;
4449 updateTabBar = 1;
4454 static void cmdDefaultFG (const char *cmdname, char *argstr) {
4455 int c;
4457 if (iniParseArguments(argstr, "i{0,511}", &c) == NULL) term->deffg = c;
4461 static void cmdDefaultBG (const char *cmdname, char *argstr) {
4462 int c;
4464 if (iniParseArguments(argstr, "i{0,511}", &c) == NULL) term->defbg = c;
4468 static void scrollHistory (int delta) {
4469 if (term->maxhistory < 1) return; // no history
4470 term->topline += delta;
4471 if (term->topline > term->maxhistory-term->row) term->topline = term->maxhistory-term->row;
4472 if (term->topline < 0) term->topline = 0;
4473 tfulldirt();
4474 draw(1);
4478 static void cmdScrollHistoryLineUp (const char *cmdname, char *argstr) {
4479 scrollHistory(1);
4483 static void cmdScrollHistoryPageUp (const char *cmdname, char *argstr) {
4484 scrollHistory(term->row);
4488 static void cmdScrollHistoryLineDown (const char *cmdname, char *argstr) {
4489 scrollHistory(-1);
4493 static void cmdScrollHistoryPageDown (const char *cmdname, char *argstr) {
4494 scrollHistory(-term->row);
4498 static void cmdScrollHistoryTop (const char *cmdname, char *argstr) {
4499 scrollHistory(term->linecount);
4503 static void cmdScrollHistoryBottom (const char *cmdname, char *argstr) {
4504 scrollHistory(-term->linecount);
4508 static void cmdUTF8Locale (const char *cmdname, char *argstr) {
4509 int b;
4511 if (iniParseArguments(argstr, "b", &b) == NULL) {
4512 if (!needConversion) b = 1;
4513 if (term != NULL) term->needConv = !b;
4514 //fprintf(stderr, "needConv: %d (%d)\n", term->needConv, needConversion);
4519 static const Command commandList[] = {
4520 {"PastePrimary", cmdPastePrimary},
4521 {"PasteSecondary", cmdPasteSecondary},
4522 {"PasteClipboard", cmdPasteClipboard},
4523 {"exec", cmdExec},
4524 {"NewTab", cmdNewTab}, // 'noswitch' 'next' 'prev' 'first' 'last'
4525 {"CloseTab", cmdCloseTab},
4526 {"KillTab", cmdKillTab},
4527 {"SwitchToTab", cmdSwitchToTab}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
4528 {"MoveTabTo", cmdMoveTabTo}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
4529 {"defaultfg", cmdDefaultFG},
4530 {"defaultbg", cmdDefaultBG},
4531 {"ScrollHistoryLineUp", cmdScrollHistoryLineUp},
4532 {"ScrollHistoryPageUp", cmdScrollHistoryPageUp},
4533 {"ScrollHistoryLineDown", cmdScrollHistoryLineDown},
4534 {"ScrollHistoryPageDown", cmdScrollHistoryPageDown},
4535 {"ScrollHistoryTop", cmdScrollHistoryTop},
4536 {"ScrollHistoryBottom", cmdScrollHistoryBottom},
4537 {"UTF8Locale", cmdUTF8Locale}, // 'on', 'off'
4539 {"term", cmdTermName},
4540 {"title", cmdWinTitle},
4541 {"tabsize", cmdTabSize},
4542 {"defaultcursorfg", cmdDefaultCursorFG},
4543 {"defaultcursorbg", cmdDefaultCursorBG},
4544 {"defaultinactivecursorfg", cmdDefaultInactiveCursorFG},
4545 {"defaultinactivecursorbg", cmdDefaultInactiveCursorBG},
4546 {"defaultboldfg", cmdDefaultBoldFG},
4547 {"defaultunderlinefg", cmdDefaultUnderlineFG},
4549 {NULL, NULL}
4553 static void executeCommand (const char *str, int slen) {
4554 const char *e;
4555 char *cmdname;
4557 if (str == NULL) return;
4558 if (slen < 0) slen = strlen(str);
4560 for (int f = 0; f < slen; ++f) if (!str[f]) { slen = f; break; }
4562 while (slen > 0 && isspace(*str)) { ++str; --slen; }
4563 if (slen < 1 || !str[0]) return;
4565 for (e = str; slen > 0 && !isspace(*e); ++e, --slen) ;
4567 if (e-str > 127) return;
4568 cmdname = alloca(e-str+8);
4569 if (cmdname == NULL) return;
4570 memcpy(cmdname, str, e-str);
4571 cmdname[e-str] = 0;
4572 while (slen > 0 && isspace(*e)) { ++e; --slen; }
4574 for (int f = 0; commandList[f].name != NULL; ++f) {
4575 if (strcasecmp(commandList[f].name, cmdname) == 0) {
4576 char *left = calloc(slen+2, 1);
4578 if (left != NULL) {
4579 if (slen > 0) memcpy(left, e, slen);
4580 //fprintf(stderr, "command: [%s]; args: [%s]\n", cmdname, left);
4581 commandList[f].fn(cmdname, left);
4582 free(left);
4584 break;
4590 static void executeCommands (const char *str) {
4591 oldTerm = term;
4592 oldTermIdx = termidx;
4593 newTerm = NULL;
4594 newTermSwitch = 0;
4595 if (str == NULL) return;
4596 while (*str) {
4597 const char *ce;
4598 char qch;
4600 while (*str && isspace(*str)) ++str;
4601 if (!*str) break;
4602 if (*str == ';') { ++str; continue; }
4604 ce = str;
4605 qch = ' ';
4606 while (*ce) {
4607 if (*ce == ';' && qch == ' ') break;
4608 if (qch != ' ' && *ce == qch) { qch = ' '; ++ce; continue; }
4609 if (*ce == '"' || *ce == '\'') {
4610 if (qch == ' ') qch = *ce;
4611 ++ce;
4612 continue;
4614 if (*ce++ == '\\' && *ce) ++ce;
4617 executeCommand(str, ce-str);
4618 if (*ce) str = ce+1; else break;
4620 flushNewTerm();
4621 switchToTerm(oldTermIdx, 1);
4625 ////////////////////////////////////////////////////////////////////////////////
4626 int main (int argc, char *argv[]) {
4627 char *configfile = NULL;
4629 //dbgLogInit();
4631 for (int f = 1; f < argc; f++) {
4632 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
4633 if (strcmp(argv[f], "-into") == 0) { ++f; continue; }
4634 if (strcmp(argv[f], "-embed") == 0) { ++f; continue; }
4635 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
4636 case 'e': f = argc+1; break;
4637 case 't':
4638 case 'c':
4639 case 'w':
4640 case 'b':
4641 ++f;
4642 break;
4643 case 'T':
4644 if (++f < argc) {
4645 free(opt_term);
4646 opt_term = strdup(argv[f]);
4647 opt_term_locked = 1;
4649 break;
4650 case 'C':
4651 if (++f < argc) {
4652 if (configfile != NULL) free(configfile);
4653 configfile = strdup(argv[f]);
4655 break;
4656 case 'v':
4657 case 'h':
4658 default:
4659 fprintf(stderr, "%s", USAGE);
4660 exit(EXIT_FAILURE);
4664 initDefaultOptions();
4665 if (configfile == NULL) {
4666 const char *home = getenv("HOME");
4668 if (home != NULL) {
4669 configfile = SPrintf("%s/.sterm.rc", home);
4670 if (loadConfig(configfile) == 0) goto cfgdone;
4671 free(configfile); configfile = NULL;
4673 configfile = SPrintf("%s/.config/sterm.rc", home);
4674 if (loadConfig(configfile) == 0) goto cfgdone;
4675 free(configfile); configfile = NULL;
4678 configfile = SPrintf("/etc/sterm.rc");
4679 if (loadConfig(configfile) == 0) goto cfgdone;
4680 free(configfile); configfile = NULL;
4682 configfile = SPrintf("/etc/sterm/sterm.rc");
4683 if (loadConfig(configfile) == 0) goto cfgdone;
4684 free(configfile); configfile = NULL;
4686 configfile = SPrintf("./.sterm.rc");
4687 if (loadConfig(configfile) == 0) goto cfgdone;
4688 free(configfile); configfile = NULL;
4689 // no config
4690 } else {
4691 if (loadConfig(configfile) < 0) die("config file '%s' not found!", configfile);
4693 cfgdone:
4694 if (configfile != NULL) free(configfile); configfile = NULL;
4696 for (int f = 1; f < argc; f++) {
4697 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
4698 if (strcmp(argv[f], "-into") == 0 || strcmp(argv[f], "-embed") == 0) {
4699 if (opt_embed) free(opt_embed);
4700 opt_embed = strdup(argv[f]);
4701 continue;
4703 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
4704 case 't':
4705 if (++f < argc) {
4706 free(opt_title);
4707 opt_title = strdup(argv[f]);
4709 break;
4710 case 'c':
4711 if (++f < argc) {
4712 free(opt_class);
4713 opt_class = strdup(argv[f]);
4715 break;
4716 case 'w':
4717 if (++f < argc) {
4718 if (opt_embed) free(opt_embed);
4719 opt_embed = strdup(argv[f]);
4721 break;
4722 case 'e':
4723 /* eat every remaining arguments */
4724 if (++f < argc) opt_cmd = &argv[f];
4725 f = argc+1;
4726 case 'T':
4727 case 'C':
4728 ++f;
4729 break;
4730 case 'v':
4731 case 'h':
4732 default:
4733 fprintf(stderr, "%s", USAGE);
4734 exit(EXIT_FAILURE);
4738 setenv("TERM", opt_term, 1);
4739 mclock_init();
4740 setlocale(LC_ALL, "");
4741 initLCConversion();
4742 updateTabBar = 1;
4743 termidx = 0;
4744 term = termalloc();
4745 if (term->execcmd != NULL) { free(term->execcmd); term->execcmd = NULL; }
4746 tinitialize(80, 25);
4747 if (ttynew(term) != 0) die("can't run process");
4748 opt_cmd = NULL;
4749 xinit();
4750 selinit();
4751 run();
4752 return 0;