colored command line
[k8sterm.git] / src / sterm.c
blobf6c3d755f34c4c1cf52237b70ce6f16535da39e4
1 /* See LICENSE for licence details. */
2 #define VERSION "0.3.1.beta7"
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 PASTE_SELECTION_DEBUG
46 //#define DUMP_KEYSYMS
48 //#define KEYPAD_DUMP
50 //#define DUMP_PROG_OUTPUT
51 //#define DUMP_PROG_INPUT
53 //#define DUMP_IO_READ
54 //#define DUMP_IO_WRITE
56 #if defined(DUMP_IO_READ) || defined(DUMP_IO_WRITE)
57 # define DUMP_IO
58 #endif
60 #ifdef KEYPAD_DUMP
61 # define DUMP_KEYPAD_SWITCH(strflag,strstate) do { fprintf(stderr, "KEYPAD %s (%s)\n", (strstate), (strflag)); } while (0)
62 #else
63 # define DUMP_KEYPAD_SWITCH(strflag,strstate) ((void)(sizeof(strflag)+sizeof(strstate)))
64 #endif
67 ////////////////////////////////////////////////////////////////////////////////
68 #define USAGE \
69 "sterm " VERSION " (c) 2010-2012 st engineers and Ketmar // Vampire Avalon\n" \
70 "usage: sterm [-t title] [-c class] [-w windowid] [-T termname] [-C config_file] [-l langiconv] [-S] [-v] [-R stcmd] [-e command...]\n"
73 ////////////////////////////////////////////////////////////////////////////////
74 #define FONT "-*-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"
75 #define FONTBOLD "-*-fixed-bold-r-normal-*-18-*-*-*-*-*-*-*"
76 #define FONTTAB "-*-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*"
79 /* Default shell to use if SHELL is not set in the env */
80 #define SHELL "/bin/sh"
83 /* Terminal colors (16 first used in escape sequence) */
84 static const char *defcolornames[] = {
85 #if 1
86 /* 8 normal colors */
87 "black",
88 "red3",
89 "green3",
90 "yellow3",
91 "blue2",
92 "magenta3",
93 "cyan3",
94 "gray90",
95 /* 8 bright colors */
96 "gray50",
97 "red",
98 "green",
99 "yellow",
100 "#5c5cff",
101 "magenta",
102 "cyan",
103 "white",
104 #else
105 /* 8 normal colors */
106 "#000000",
107 "#b21818",
108 "#18b218",
109 "#b26818",
110 "#1818b2",
111 "#b218b2",
112 "#18b2b2",
113 "#b2b2b2",
114 /* 8 bright colors */
115 "#686868",
116 "#ff5454",
117 "#54ff54",
118 "#ffff54",
119 "#5454ff",
120 "#ff54ff",
121 "#54ffff",
122 "#ffffff",
123 #endif
127 /* more colors can be added after 255 to use with DefaultXX */
128 static const char *defextcolornames[] = {
129 "#cccccc", /* 256 */
130 "#333333", /* 257 */
131 /* root terminal fg and bg */
132 "#809a70", /* 258 */
133 "#002000", /* 259 */
134 /* bold and underline */
135 "#00afaf", /* 260 */
136 "#00af00", /* 261 */
140 /* Default colors (colorname index) foreground, background, cursor, unfocused cursor */
141 #define DEFAULT_FG (7)
142 #define DEFAULT_BG (0)
143 #define DEFAULT_CS (256)
144 #define DEFAULT_UCS (257)
146 #define TNAME "xterm"
148 /* double-click timeout (in milliseconds) between clicks for selection */
149 #define DOUBLECLICK_TIMEOUT (300)
150 #define TRIPLECLICK_TIMEOUT (2*DOUBLECLICK_TIMEOUT)
151 //#define SELECT_TIMEOUT 20 /* 20 ms */
152 #define DRAW_TIMEOUT 18 /* 18 ms */
154 #define TAB (8)
157 ////////////////////////////////////////////////////////////////////////////////
158 #define MAX_COLOR (511)
160 /* XEMBED messages */
161 #define XEMBED_FOCUS_IN (4)
162 #define XEMBED_FOCUS_OUT (5)
165 /* Arbitrary sizes */
166 #define ESC_TITLE_SIZ (256)
167 #define ESC_BUF_SIZ (256)
168 #define ESC_ARG_SIZ (16)
169 #define DRAW_BUF_SIZ (2048)
170 #define UTF_SIZ (4)
171 #define OBUFSIZ (256)
172 #define WBUFSIZ (256)
175 /* masks for key translation */
176 #define XK_NO_MOD (UINT_MAX)
177 #define XK_ANY_MOD (0)
180 /* misc utility macros */
181 //#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000+(t1.tv_usec-t2.tv_usec)/1000)
182 #define SERRNO strerror(errno)
183 #define MIN(a, b) ((a) < (b) ? (a) : (b))
184 #define MAX(a, b) ((a) < (b) ? (b) : (a))
185 #define LEN(a) (sizeof(a)/sizeof(a[0]))
186 #define DEFAULT(a, b) (a) = ((a) ? (a) : (b))
187 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
188 #define LIMIT(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x))
189 #define ATTRCMP(a, b) ((a).attr != (b).attr || (a).fg != (b).fg || (a).bg != (b).bg)
190 #define IS_SET(flag) (term->mode&(flag))
191 #define X2COL(x) ((x)/xw.cw)
192 #define Y2ROW(y) ((y-(opt_tabposition==1 ? xw.tabheight : 0))/xw.ch-(term!=NULL ? term->topline : 0))
193 #define IS_GFX(mode) ((mode)&ATTR_GFX)
196 ////////////////////////////////////////////////////////////////////////////////
197 enum {
198 BELL_AUDIO = 0x01,
199 BELL_URGENT = 0x02
202 enum {
203 CMDMODE_NONE,
204 CMDMODE_INPUT,
205 CMDMODE_MESSAGE
208 enum glyph_attribute {
209 ATTR_NULL = 0x00,
210 ATTR_REVERSE = 0x01,
211 ATTR_UNDERLINE = 0x02,
212 ATTR_BOLD = 0x04,
213 ATTR_GFX = 0x08,
214 ATTR_DEFFG = 0x10,
215 ATTR_DEFBG = 0x20,
218 enum cursor_movement {
219 CURSOR_UP,
220 CURSOR_DOWN,
221 CURSOR_LEFT,
222 CURSOR_RIGHT,
223 CURSOR_SAVE,
224 CURSOR_LOAD
227 enum cursor_state {
228 CURSOR_DEFAULT = 0x00,
229 CURSOR_HIDE = 0x01,
230 CURSOR_WRAPNEXT = 0x02
233 enum glyph_state {
234 GLYPH_SET = 0x01, /* for selection only */
235 GLYPH_DIRTY = 0x02,
236 GLYPH_WRAP = 0x10, /* can be set for the last line glyph */
240 enum term_mode {
241 MODE_WRAP = 0x01,
242 MODE_INSERT = 0x02,
243 MODE_APPKEYPAD = 0x04,
244 MODE_ALTSCREEN = 0x08,
245 MODE_CRLF = 0x10,
246 MODE_MOUSEBTN = 0x20,
247 MODE_MOUSEMOTION = 0x40,
248 MODE_MOUSE = 0x20|0x40,
249 MODE_REVERSE = 0x80,
250 MODE_BRACPASTE = 0x100,
251 MODE_FOCUSEVT = 0x200,
252 MODE_DISPCTRL = 0x400, //TODO: not implemented yet
253 MODE_GFX0 = 0x1000,
254 MODE_GFX1 = 0x2000,
257 enum escape_state {
258 ESC_START = 0x01,
259 ESC_CSI = 0x02,
260 ESC_OSC = 0x04,
261 ESC_TITLE = 0x08,
262 ESC_ALTCHARSET = 0x10,
263 ESC_HASH = 0x20,
264 ESC_PERCENT = 0x40,
265 ESC_ALTG1 = 0x80,
268 enum window_state {
269 WIN_VISIBLE = 0x01,
270 WIN_REDRAW = 0x02,
271 WIN_FOCUSED = 0x04,
274 /* bit macro */
275 #undef B0
276 enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 };
279 ////////////////////////////////////////////////////////////////////////////////
280 typedef unsigned char uchar;
281 typedef unsigned int uint;
282 typedef unsigned long ulong;
283 typedef unsigned short ushort;
286 typedef struct __attribute__((packed)) {
287 char c[UTF_SIZ]; /* character code */
288 uchar attr; /* attribute flags */
289 ushort fg; /* foreground */
290 ushort bg; /* background */
291 uchar state; /* state flags */
292 } Glyph;
295 typedef Glyph *Line;
297 typedef struct {
298 Glyph attr; /* current char attributes */
299 int x;
300 int y;
301 char state;
302 } TCursor;
305 /* CSI Escape sequence structs */
306 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
307 typedef struct {
308 char buf[ESC_BUF_SIZ]; /* raw string */
309 int len; /* raw string length */
310 char priv;
311 int arg[ESC_ARG_SIZ];
312 int narg; /* nb of args */
313 char mode;
314 } CSIEscape;
317 /* Purely graphic info */
318 typedef struct {
319 Display *dpy;
320 Colormap cmap;
321 Window win;
322 Cursor cursor;
323 Cursor blankPtr;
324 Atom xembed;
325 XIM xim;
326 XIC xic;
327 int scr;
328 int w; /* window width */
329 int h; /* window height */
330 int bufw; /* pixmap width */
331 int bufh; /* pixmap height */
332 int ch; /* char height */
333 int cw; /* char width */
334 char state; /* focus, redraw, visible */
336 int tch; /* tab text char height */
337 Pixmap pictab;
338 int tabheight;
339 //struct timeval lastdraw;
340 } XWindow;
343 /* TODO: use better name for vars... */
344 typedef struct {
345 int mode;
346 int bx, by;
347 int ex, ey;
348 struct { int x, y; } b, e;
349 char *clip;
350 Atom xtarget;
351 int tclick1;
352 int tclick2;
353 } Selection;
356 /* Drawing Context */
357 typedef struct {
358 ulong *ncol; // normal colors
359 ulong *bcol; // b/w colors
360 ulong *gcol; // green colors
361 ulong *clrs[3];
362 GC gc;
363 struct {
364 int ascent;
365 int descent;
366 short lbearing;
367 short rbearing;
368 XFontSet set;
369 Font fid;
370 } font[3];
371 } DC;
374 typedef void (*CmdLineExecFn) (int cancelled);
377 #define CMDLINE_SIZE (256)
379 /* Internal representation of the screen */
380 typedef struct Term {
381 int cmdfd;
382 int dead;
383 int exitcode;
384 int needConv; /* 0: utf-8 locale */
385 int belltype;
386 int blackandwhite;
388 int curblink;
389 int curbhidden;
390 int lastBlinkTime;
391 int curblinkinactive;
393 int row; /* nb row */
394 int col; /* nb col */
395 int topline; /* top line for drawing (0: no history; 1: show one history line; etc) */
396 int linecount; /* full, with history */
397 int maxhistory;/* max history lines; 0: none; <0: infinite */
398 Line *line; /* screen */
399 Line *alt; /* alternate screen */
400 char *dirty; /* dirtyness of lines */
401 TCursor c; /* cursor */
402 int top; /* top scroll limit */
403 int bot; /* bottom scroll limit */
404 int mode; /* terminal mode flags */
405 int mousemode; /* mouse mode: 1000, 1005, 1006, 1015 */
406 int esc; /* escape state flags */
407 int charset; /* 0 or 1 */
409 TCursor csaved; /* saved cursor info */
410 // old cursor position
411 int oldcx;
412 int oldcy;
414 char title[ESC_TITLE_SIZ+1];
415 int titlelen;
417 int mouseob;
418 int mouseox;
419 int mouseoy;
421 char obuf[OBUFSIZ];
422 #ifdef DUMP_PROG_OUTPUT
423 int xobuflen;
424 #endif
425 int obuflen;
427 char ubuf[UTF_SIZ];
428 int ubufpos;
430 char drawbuf[DRAW_BUF_SIZ];
432 char wrbuf[WBUFSIZ];
433 int wrbufsize;
434 int wrbufused;
435 int wrbufpos;
437 CSIEscape escseq;
438 Selection sel;
439 pid_t pid;
440 int lastDrawTime;
442 char *execcmd;
444 Pixmap picbuf;
445 int picbufw;
446 int picbufh;
448 ushort deffg;
449 ushort defbg;
451 int wantRedraw;
453 int lastActiveTime;
455 int cmdMode;
456 char cmdline[UTF_SIZ*CMDLINE_SIZE];
457 int cmdreslen; // byte length of 'reserved' (read-only) part
458 int cmdofs;
459 char cmdc[UTF_SIZ+1];
460 int cmdcl;
461 int cmdtabpos;
462 const char *cmdprevc;
463 CmdLineExecFn cmdexecfn;
464 } Term;
467 ////////////////////////////////////////////////////////////////////////////////
468 /* Globals */
469 static ushort *unimap = NULL; // 0 or 65535 items; 127: special; high bit set: use alt(gfx) mode; 0: empty
471 static char **opt_cmd = NULL;
472 static char *opt_title = NULL;
473 static char *opt_embed = NULL;
474 static char *opt_class = NULL;
475 static char *opt_term = NULL;
476 static char *opt_fontnorm = NULL;
477 static char *opt_fontbold = NULL;
478 static char *opt_fonttab = NULL;
479 static char *opt_shell = NULL;
480 static char *opt_colornames[512];
481 static int opt_term_locked = 0;
482 static int opt_doubleclick_timeout = DOUBLECLICK_TIMEOUT;
483 static int opt_tripleclick_timeout = TRIPLECLICK_TIMEOUT;
484 static int opt_tabsize = TAB;
485 static int defaultFG = DEFAULT_FG;
486 static int defaultBG = DEFAULT_BG;
487 static int defaultCursorFG = 0;
488 static int defaultCursorBG = DEFAULT_CS;
489 static int defaultCursorInactiveFG = 0;
490 static int defaultCursorInactiveBG = DEFAULT_UCS;
491 static int defaultBoldFG = -1;
492 static int defaultUnderlineFG = -1;
493 static int normalTabFG = 258;
494 static int normalTabBG = 257;
495 static int activeTabFG = 258;
496 static int activeTabBG = 0;
497 static int opt_maxhistory = 512;
498 static int opt_ptrblank = 2000; // delay; 0: never
499 static int opt_tabcount = 6;
500 static int opt_tabposition = 0; // 0: bottom; 1: top
501 static int opt_drawtimeout = DRAW_TIMEOUT;
502 static int opt_disabletabs = 0;
503 static int opt_audiblebell = 1;
504 static int opt_urgentbell = 1;
505 static int opt_cursorBlink = 0;
506 static int opt_cursorBlinkInactive = 0;
507 static int opt_drawunderline = 1;
508 static int opt_ignoreclose = 0;
509 static int opt_maxdrawtimeout = 3000;
510 static int ptrBlanked = 0;
511 static int ptrLastMove = 0;
512 static int globalBW = 0;
514 static Term **term_array = NULL;
515 static int term_count = 0;
516 static int term_array_size = 0;
517 static Term *term; // current terminal
518 static int termidx; // current terminal index; DON'T RELAY ON IT!
519 static int updateTabBar;
520 static int lastDrawTime = 0;
521 static char *lastSelStr = NULL;
522 //static int lastSelLength = 0;
524 static int firstVisibleTab = 0;
526 static int exitcode = 0;
527 static int closeRequestComes = 0;
529 static DC dc;
530 static XWindow xw;
532 static Atom XA_VT_SELECTION;
533 static Atom XA_CLIPBOARD;
534 static Atom XA_UTF8;
535 static Atom XA_TARGETS;
536 static Atom XA_NETWM_NAME;
537 static Atom XA_WM_DELETE_WINDOW;
540 ////////////////////////////////////////////////////////////////////////////////
541 typedef struct {
542 KeySym src;
543 KeySym dst;
544 } KeyTransDef;
547 static KeyTransDef *keytrans = NULL;
548 static int keytrans_size = 0;
549 static int keytrans_used = 0;
552 typedef struct {
553 KeySym key;
554 uint mask;
555 int kp;
556 char *str;
557 } KeyInfoDef;
560 static KeyInfoDef *keybinds = NULL;
561 static int keybinds_size = 0;
562 static int keybinds_used = 0;
564 static KeyInfoDef *keymap = NULL;
565 static int keymap_size = 0;
566 static int keymap_used = 0;
569 ////////////////////////////////////////////////////////////////////////////////
570 static void executeCommands (const char *str);
571 static const char *findCommandCompletion (const char *str, int slen, const char *prev);
573 static void ttyresize (void);
574 static void tputc (const char *c); // `c` is utf-8
575 static void ttywrite (const char *s, size_t n);
576 static void tsetdirt (int top, int bot);
577 static void tfulldirt (void);
579 static void xclearunused (void);
580 static void xseturgency (int add);
581 static void xfixsel (void);
582 static void xblankPointer (void);
583 static void xunblankPointer (void);
584 static void xdrawTabBar (void);
586 static void draw (int forced);
588 static void tcmdput (const char *s, int len);
591 static inline void ttywritestr (const char *s) { if (s != NULL && s[0]) ttywrite(s, strlen(s)); }
594 static inline ulong getColor (int idx) {
595 if (globalBW && (term == NULL || !term->blackandwhite)) return dc.clrs[globalBW][idx];
596 if (term != NULL) return dc.clrs[term->blackandwhite%3][idx];
597 return dc.clrs[0][idx];
599 if (globalBW) return dc.bcol[idx];
600 if (term != NULL) {
601 return (term->blackandwhite ? dc.bcol[idx] : dc.ncol[idx]);
603 return dc.ncol[idx];
608 ////////////////////////////////////////////////////////////////////////////////
610 static void trimstr (char *s) {
611 char *e;
613 while (*s && isspace(*s)) ++s;
614 for (e = s+strlen(s); e > s; --e) if (!isspace(e[-1])) break;
615 if (e <= s) *s = 0; else *e = 0;
619 // parse the argument list
620 // return error message or NULL
621 // format:
622 // '*': skip
623 // 's': string (char *)
624 // 'i': integer (int *)
625 // 'b': boolean (int *)
626 // '|': optional arguments follows
627 // '.': stop parsing, ignore rest
628 // 'R': stop parsing, set rest ptr (char *)
629 // string modifiers (also for 'R'):
630 // '!' -- don't allow empty strings
631 // '-' -- trim spaces
632 // int modifiers:
633 // {lo,hi}
634 // {,hi}
635 // {lo}
636 // WARNING! `line` will be modified!
637 // WARNING! string pointers will point INSIDE `line`, so don't discard it!
638 // UGLY! REWRITE!
639 const char *iniParseArguments (char *line, const char *fmt, ...) {
640 va_list ap;
641 int inOptional = 0;
643 if (line == NULL) return "alas";
644 trimstr(line);
645 va_start(ap, fmt);
646 while (*fmt) {
647 char spec = *fmt++, *args;
649 if (spec == '|') { inOptional = 1; continue; }
650 if (spec == '.') { va_end(ap); return NULL; }
652 while (*line && isspace(*line)) ++line;
653 if (*line == '#') *line = 0;
655 if (spec == 'R') {
656 char **p = va_arg(ap, char **);
657 int noempty = 0;
659 while (*fmt) {
660 if (*fmt == '!') { ++fmt; noempty = 1; }
661 else if (*fmt == '-') { ++fmt; trimstr(line); }
662 else break;
664 va_end(ap);
665 if (noempty && !line[0]) return "invalid empty arg";
666 if (p != NULL) *p = line;
667 return NULL;
670 if (!line[0]) {
671 // end of line, stop right here
672 va_end(ap);
673 if (!inOptional) return "out of args";
674 return NULL;
677 args = line;
679 char *dest = args, qch = '#';
680 int n;
682 if (line[0] == '"' || line[0] == '\'') qch = *line++;
684 while (*line && *line != qch) {
685 if (qch == '#' && isspace(*line)) break;
687 if (*line == '\\') {
688 if (!line[1]) { va_end(ap); return "invalid escape"; }
689 switch (*(++line)) {
690 case 'n': *dest++ = '\n'; ++line; break;
691 case 'r': *dest++ = '\r'; ++line; break;
692 case 't': *dest++ = '\t'; ++line; break;
693 case 'a': *dest++ = '\a'; ++line; break;
694 case 'e': *dest++ = '\x1b'; ++line; break; // esc
695 case 's': *dest++ = ' '; ++line; break;
696 case 'x': // hex
697 ++line;
698 if (!isxdigit(*line)) { va_end(ap); return "invalid hex escape"; }
699 n = toupper(*line)-'0'; if (n > 9) n -= 7;
700 ++line;
701 if (isxdigit(*line)) {
702 int b = toupper(*line)-'0'; if (b > 9) b -= 7;
704 n = (n*16)+b;
705 ++line;
707 *dest++ = n;
708 break;
709 case '0': // octal
710 n = 0;
711 for (int f = 0; f < 4; ++f) {
712 if (*line < '0' || *line > '7') break;
713 n = (n*8)+(line[0]-'0');
714 if (n > 255) { va_end(ap); return "invalid oct escape"; }
715 ++line;
717 if (n == 0) { va_end(ap); return "invalid oct escape"; }
718 *dest++ = n;
719 break;
720 case '1'...'9': // decimal
721 n = 0;
722 for (int f = 0; f < 3; ++f) {
723 if (*line < '0' || *line > '9') break;
724 n = (n*8)+(line[0]-'0');
725 if (n > 255) { va_end(ap); return "invalid dec escape"; }
726 ++line;
728 if (n == 0) { va_end(ap); return "invalid oct escape"; }
729 *dest++ = n;
730 break;
731 default:
732 *dest++ = *line++;
733 break;
735 } else {
736 *dest++ = *line++;
739 if (qch != '#') {
740 if (*line != qch) return "unfinished string";
741 if (*line) ++line;
742 } else if (*line && *line != '#') ++line;
743 *dest = 0;
745 // now process and convert argument
746 switch (spec) {
747 case '*': /* skip */
748 break;
749 case 's': { /* string */
750 int noempty = 0, trim = 0;
751 char **p;
753 for (;;) {
754 if (*fmt == '!') { noempty = 1; ++fmt; }
755 else if (*fmt == '-') { trim = 1; ++fmt; }
756 else break;
759 if (trim) trimstr(args);
761 if (noempty && !args[0]) { va_end(ap); return "invalid empty string"; }
762 p = va_arg(ap, char **);
763 if (p != NULL) *p = args;
764 } break;
765 case 'i': /* int */
766 if (!args[0]) {
767 va_end(ap);
768 return "invalid integer";
769 } else {
770 int *p = va_arg(ap, int *);
771 long int n;
772 char *eptr;
774 trimstr(args);
775 n = strtol(args, &eptr, 0);
776 if (*eptr) { va_end(ap); return "invalid integer"; }
778 if (*fmt == '{') {
779 // check min/max
780 int minmax[2], haveminmax[2];
782 haveminmax[0] = haveminmax[1] = 0;
783 minmax[0] = minmax[1] = 0;
784 ++fmt;
785 for (int f = 0; f < 2; ++f) {
786 if (isdigit(*fmt) || *fmt == '-' || *fmt == '+') {
787 int neg = 0;
788 haveminmax[f] = 1;
789 if (*fmt == '-') neg = 1;
790 if (!isdigit(*fmt)) {
791 ++fmt;
792 if (!isdigit(*fmt)) { va_end(ap); return "invalid integer bounds"; }
794 while (isdigit(*fmt)) {
795 minmax[f] = minmax[f]*10+(fmt[0]-'0');
796 ++fmt;
798 if (neg) minmax[f] = -minmax[f];
799 //fprintf(stderr, "got: %d\n", minmax[f]);
801 if (*fmt == ',') {
802 if (f == 1) { va_end(ap); return "invalid integer bounds: extra comma"; }
803 // do nothing, we are happy
804 ++fmt;
805 } else if (*fmt == '}') {
806 // ok, done
807 break;
808 } else { va_end(ap); return "invalid integer bounds"; }
810 if (*fmt != '}') { va_end(ap); return "invalid integer bounds"; }
811 ++fmt;
813 //fprintf(stderr, "b: (%d,%d) (%d,%d)\n", haveminmax[0], minmax[0], haveminmax[1], minmax[1]);
814 if ((haveminmax[0] && n < minmax[0]) || (haveminmax[1] && n > minmax[1])) { va_end(ap); return "integer out of bounds"; }
817 if (p) *p = n;
819 break;
820 case 'b': { /* bool */
821 int *p = va_arg(ap, int *);
823 trimstr(args);
824 if (!args[0]) { va_end(ap); return "invalid boolean"; }
825 if (strcasecmp(args, "true") == 0 || strcasecmp(args, "on") == 0 ||
826 strcasecmp(args, "tan") == 0 || strcasecmp(args, "1") == 0) {
827 if (p) *p = 1;
828 } else if (strcasecmp(args, "false") == 0 || strcasecmp(args, "off") == 0 ||
829 strcasecmp(args, "ona") == 0 || strcasecmp(args, "0") == 0) {
831 if (p) *p = 0;
832 } else {
833 va_end(ap);
834 return "invalid boolean";
836 } break;
837 default:
838 va_end(ap);
839 return "invalid format specifier";
842 va_end(ap);
843 while (*line && isspace(*line)) ++line;
844 if (!line[0] || line[0] == '#') return NULL;
845 return "extra args";
849 ////////////////////////////////////////////////////////////////////////////////
850 // UTF-8
851 static int utf8decode (const char *s, ulong *u) {
852 uchar c;
853 int n, rtn;
855 rtn = 1;
856 c = *s;
857 if (~c & B7) { /* 0xxxxxxx */
858 *u = c;
859 return rtn;
860 } else if ((c & (B7|B6|B5)) == (B7|B6)) { /* 110xxxxx */
861 *u = c&(B4|B3|B2|B1|B0);
862 n = 1;
863 } else if ((c & (B7|B6|B5|B4)) == (B7|B6|B5)) { /* 1110xxxx */
864 *u = c&(B3|B2|B1|B0);
865 n = 2;
866 } else if ((c & (B7|B6|B5|B4|B3)) == (B7|B6|B5|B4)) { /* 11110xxx */
867 *u = c & (B2|B1|B0);
868 n = 3;
869 } else {
870 goto invalid;
872 ++s;
873 for (int f = n; f > 0; --f, ++rtn, ++s) {
874 c = *s;
875 if ((c & (B7|B6)) != B7) goto invalid; /* 10xxxxxx */
876 *u <<= 6;
877 *u |= c & (B5|B4|B3|B2|B1|B0);
879 if ((n == 1 && *u < 0x80) ||
880 (n == 2 && *u < 0x800) ||
881 (n == 3 && *u < 0x10000) ||
882 (*u >= 0xD800 && *u <= 0xDFFF)) {
883 goto invalid;
885 return rtn;
886 invalid:
887 *u = 0xFFFD;
888 return rtn;
892 static int utf8encode (ulong u, char *s) {
893 uchar *sp;
894 ulong uc;
895 int n;
897 sp = (uchar *)s;
898 uc = u&0x1fffff;
899 if (uc < 0x80) {
900 *sp = uc; /* 0xxxxxxx */
901 return 1;
902 } else if (uc < 0x800) {
903 *sp = (uc >> 6) | (B7|B6); /* 110xxxxx */
904 n = 1;
905 } else if (uc < 0x10000) {
906 *sp = (uc >> 12) | (B7|B6|B5); /* 1110xxxx */
907 n = 2;
908 } else if (uc <= 0x10FFFF) {
909 *sp = (uc >> 18) | (B7|B6|B5|B4); /* 11110xxx */
910 n = 3;
911 } else {
912 goto invalid;
914 ++sp;
915 for (int f = n; f > 0; --f, ++sp) *sp = ((uc >> 6*(f-1)) & (B5|B4|B3|B2|B1|B0)) | B7; /* 10xxxxxx */
916 return n+1;
917 invalid:
918 /* U+FFFD */
919 *s++ = '\xEF';
920 *s++ = '\xBF';
921 *s = '\xBD';
922 return 3;
926 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
927 UTF-8 otherwise return 0 */
928 static int isfullutf8 (const char *s, int b) {
929 uchar *c1, *c2, *c3;
931 c1 = (uchar *) s;
932 c2 = (uchar *) ++s;
933 c3 = (uchar *) ++s;
934 if (b < 1) return 0;
935 if ((*c1&(B7|B6|B5)) == (B7|B6) && b == 1) return 0;
936 if ((*c1&(B7|B6|B5|B4)) == (B7|B6|B5) && (b == 1 || (b == 2 && (*c2&(B7|B6)) == B7))) return 0;
937 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;
938 return 1;
942 static int utf8size (const char *s) {
943 uchar c = *s;
945 if (~c&B7) return 1;
946 if ((c&(B7|B6|B5)) == (B7|B6)) return 2;
947 if ((c&(B7|B6|B5|B4)) == (B7|B6|B5)) return 3;
948 return 4;
952 static int utf8strlen (const char *s) {
953 int len = 0;
955 while (*s) {
956 if (((unsigned char)(s[0])&0xc0) == 0xc0 || ((unsigned char)(s[0])&0x80) == 0) ++len;
957 ++s;
959 return len;
963 static void utf8choplast (char *s) {
964 int lastpos = 0;
966 for (char *t = s; *t; ++t) {
967 if (((unsigned char)(t[0])&0xc0) == 0xc0 || ((unsigned char)(t[0])&0x80) == 0) lastpos = (int)(t-s);
969 s[lastpos] = 0;
973 ////////////////////////////////////////////////////////////////////////////////
974 // utilities
975 static char *SPrintfVA (const char *fmt, va_list vaorig) {
976 char *buf = NULL;
977 int olen, len = 128;
979 buf = malloc(len);
980 if (buf == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
981 for (;;) {
982 char *nb;
983 va_list va;
985 va_copy(va, vaorig);
986 olen = vsnprintf(buf, len, fmt, va);
987 va_end(va);
988 if (olen >= 0 && olen < len) return buf;
989 if (olen < 0) olen = len*2-1;
990 nb = realloc(buf, olen+1);
991 if (nb == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
992 buf = nb;
993 len = olen+1;
998 static __attribute__((format(printf,1,2))) char *SPrintf (const char *fmt, ...) {
999 char *buf = NULL;
1000 va_list va;
1002 va_start(va, fmt);
1003 buf = SPrintfVA(fmt, va);
1004 va_end(va);
1005 return buf;
1009 static __attribute__((noreturn)) __attribute__((format(printf,1,2))) void die (const char *errstr, ...) {
1010 va_list ap;
1012 fprintf(stderr, "FATAL: ");
1013 va_start(ap, errstr);
1014 vfprintf(stderr, errstr, ap);
1015 va_end(ap);
1016 fprintf(stderr, "\n");
1017 exit(EXIT_FAILURE);
1021 ////////////////////////////////////////////////////////////////////////////////
1022 // getticks
1023 static struct timespec mclk_sttime; // starting time of monotonic clock
1026 static void mclock_init (void) {
1027 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &mclk_sttime);
1031 static uint mclock_ticks (void) {
1032 struct timespec tp;
1034 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &tp);
1035 tp.tv_sec -= mclk_sttime.tv_sec;
1036 return tp.tv_sec*1000+(tp.tv_nsec/1000000);
1040 ////////////////////////////////////////////////////////////////////////////////
1041 // locale conversions
1042 static iconv_t icFromLoc;
1043 static iconv_t icToLoc;
1044 static int needConversion = 0;
1045 static const char *cliLocale = NULL;
1048 static void initLCConversion (void) {
1049 const char *lct = setlocale(LC_CTYPE, NULL);
1050 char *cstr;
1052 needConversion = 0;
1053 if (cliLocale == NULL) {
1054 if (strrchr(lct, '.') != NULL) lct = strrchr(lct, '.')+1;
1055 } else {
1056 lct = cliLocale;
1058 if (strcasecmp(lct, "utf8") == 0 || strcasecmp(lct, "utf-8") == 0) return;
1059 //fprintf(stderr, "locale: [%s]\n", lct);
1060 icFromLoc = iconv_open("UTF-8", lct);
1061 if (icFromLoc == (iconv_t)-1) die("can't initialize locale conversion");
1062 cstr = SPrintf("%s//TRANSLIT", lct);
1063 icToLoc = iconv_open(cstr, "UTF-8");
1064 free(cstr);
1065 if (icToLoc == (iconv_t)-1) die("can't initialize locale conversion");
1066 needConversion = 1;
1070 static int loc2utf (char *dest, const char *src, int len) {
1071 if (needConversion) {
1072 char *ibuf, *obuf;
1073 size_t il, ol, ool;
1075 ibuf = (char *)src;
1076 obuf = dest;
1077 il = len;
1078 ool = ol = il*4;
1079 il = iconv(icFromLoc, &ibuf, &il, &obuf, &ol);
1080 if (il == (size_t)-1) return 0;
1081 return ool-ol;
1082 } else {
1083 if (len > 0) memmove(dest, src, len);
1084 return len;
1089 static int utf2loc (char *dest, const char *src, int len) {
1090 if (needConversion) {
1091 char *ibuf, *obuf;
1092 size_t il, ol, ool;
1094 ibuf = (char *)src;
1095 obuf = dest;
1096 il = len;
1097 ool = ol = il*4;
1098 il = iconv(icToLoc, &ibuf, &il, &obuf, &ol);
1099 if (il == (size_t)-1) return 0;
1100 return ool-ol;
1101 } else {
1102 if (len > 0) memmove(dest, src, len);
1103 return len;
1108 ////////////////////////////////////////////////////////////////////////////////
1109 static void fixWindowTitle (const Term *t) {
1110 const char *title = (t != NULL) ? t->title : NULL;
1112 if (title == NULL || !title[0]) {
1113 title = opt_title;
1114 if (title == NULL) title = "";
1116 XStoreName(xw.dpy, xw.win, title);
1117 XChangeProperty(xw.dpy, xw.win, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, (const unsigned char *)title, strlen(title));
1121 // find latest active terminal (but not current %-)
1122 static int findTermToSwitch (void) {
1123 int maxlat = -1, idx = -1;
1125 for (int f = 0; f < term_count; ++f) {
1126 if (term != term_array[f] && term_array[f]->lastActiveTime > maxlat) {
1127 maxlat = term_array[f]->lastActiveTime;
1128 idx = f;
1131 if (idx < 0) {
1132 if (termidx == 0) idx = 0; else idx = termidx+1;
1133 if (idx > term_count) idx = term_count-1;
1135 return idx;
1139 static void fixFirstTab (void) {
1140 if (termidx < firstVisibleTab) firstVisibleTab = termidx;
1141 else if (termidx > firstVisibleTab+opt_tabcount-1) firstVisibleTab = termidx-opt_tabcount+1;
1142 if (firstVisibleTab < 0) firstVisibleTab = 0;
1143 updateTabBar = 1;
1147 static void switchToTerm (int idx, int redraw) {
1148 if (idx >= 0 && idx < term_count && term_array[idx] != NULL && term_array[idx] != term) {
1149 int tt = mclock_ticks();;
1151 if (term != NULL) term->lastActiveTime = tt;
1152 termidx = idx;
1153 term = term_array[termidx];
1154 term->curbhidden = 0;
1155 term->lastBlinkTime = tt;
1157 fixFirstTab();
1159 xseturgency(0);
1160 tfulldirt();
1161 fixWindowTitle(term);
1162 updateTabBar = 1;
1163 XSetWindowBackground(xw.dpy, xw.win, getColor(term->defbg));
1164 xclearunused();
1165 if (redraw) draw(1);
1166 //FIXME: optimize memory allocations
1167 if (term->sel.clip != NULL && term->sel.bx >= 0) {
1168 if (lastSelStr != NULL) free(lastSelStr);
1169 lastSelStr = strdup(term->sel.clip);
1170 //fprintf(stderr, "newsel: [%s]\n", lastSelStr);
1172 xfixsel();
1173 //fprintf(stderr, "term #%d\n", termidx);
1174 //fprintf(stderr, "needConv: %d\n", term->needConv);
1179 static Term *termalloc (void) {
1180 Term *t;
1182 if (term_count >= term_array_size) {
1183 int newsz = (term_count==0) ? 1 : term_array_size+64;
1184 Term **n = realloc(term_array, sizeof(Term *)*newsz);
1186 if (n == NULL && term_count == 0) die("out of memory!");
1187 term_array = n;
1188 term_array_size = newsz;
1190 if ((t = calloc(1, sizeof(Term))) == NULL) return NULL;
1191 t->wrbufsize = WBUFSIZ;
1192 t->deffg = defaultFG;
1193 t->defbg = defaultBG;
1194 t->dead = 1;
1195 t->needConv = (needConversion ? 1 : 0);
1196 t->belltype = (opt_audiblebell?BELL_AUDIO:0)|(opt_urgentbell?BELL_URGENT:0);
1197 t->curblink = opt_cursorBlink;
1198 t->curblinkinactive = opt_cursorBlinkInactive;
1199 term_array[term_count++] = t;
1200 return t;
1204 // newer delete last terminal!
1205 static void termfree (int idx) {
1206 if (idx >= 0 && idx < term_count && term_array[idx] != NULL) {
1207 Term *t = term_array[idx];
1209 if (t->pid != 0) {
1210 kill(t->pid, SIGKILL);
1211 return;
1213 if (t->cmdfd >= 0) {
1214 close(t->cmdfd);
1215 t->cmdfd = -1;
1217 exitcode = t->exitcode;
1218 if (idx == termidx) {
1219 if (term_count > 1) {
1220 t->dead = 1;
1221 //switchToTerm((idx > 0) ? idx-1 : 1, 0);
1222 switchToTerm(findTermToSwitch(), 0);
1223 return;
1225 term = NULL;
1227 for (int y = 0; y < t->row; ++y) free(t->alt[y]);
1228 for (int y = 0; y < t->linecount; ++y) {
1229 //fprintf(stderr, "y=%d\n", y);
1230 free(t->line[y]);
1232 free(t->dirty);
1233 free(t->alt);
1234 free(t->line);
1235 if (t->execcmd != NULL) free(t->execcmd);
1236 // condense array
1237 if (termidx > idx) {
1238 // not current, and current at the right
1239 --termidx;
1241 for (int f = idx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
1242 --term_count;
1243 XFreePixmap(xw.dpy, t->picbuf);
1244 free(t);
1249 static void termcleanup (void) {
1250 int f = 0, needredraw = 0;
1252 while (f < term_count) {
1253 if (term_array[f]->dead) {
1254 termfree(f);
1255 needredraw = 1;
1256 } else {
1257 ++f;
1260 if (needredraw) {
1261 updateTabBar = 1;
1262 draw(1);
1267 //FIXME: is it safe to assume that signal interrupted main program?
1268 static void sigchld (int a) {
1269 //if (waitpid(term->pid, &stat, 0) < 0) die("waiting for pid %hd failed: %s", term->pid, SERRNO);
1270 for (;;) {
1271 int stat = 0;
1272 pid_t res = waitpid(-1, &stat, WNOHANG);
1274 if (res == (pid_t)-1 || res == 0) break;
1276 for (int f = 0; f < term_count; ++f) {
1277 if (term_array[f]->pid == res) {
1278 // this terminal should die
1279 //if (term_count == 1) exit(0);
1280 //close(term_array[f]->cmdfd);
1281 //term_array[f]->cmdfd = -1;
1282 term_array[f]->dead = 1;
1283 term_array[f]->pid = 0;
1284 term_array[f]->exitcode = (WIFEXITED(stat)) ? WEXITSTATUS(stat) : 127;
1285 //fprintf(stderr, "exitcode=%d\n", term_array[f]->exitcode);
1286 updateTabBar = 1;
1287 break;
1291 signal(SIGCHLD, sigchld);
1295 ////////////////////////////////////////////////////////////////////////////////
1296 static void keytrans_reset (void) {
1297 if (keytrans) free(keytrans);
1298 keytrans = NULL;
1299 keytrans_size = 0;
1300 keytrans_used = 0;
1304 static void keytrans_add (const char *src, const char *dst) {
1305 KeySym kssrc = XStringToKeysym(src);
1306 KeySym ksdst = XStringToKeysym(dst);
1308 if (kssrc == NoSymbol) die("invalid keysym: '%s'", src);
1309 if (ksdst == NoSymbol) die("invalid keysym: '%s'", dst);
1310 if (kssrc == ksdst) return; // idiot
1312 for (int f = 0; f < keytrans_used; ++f) {
1313 if (keytrans[f].src == kssrc) {
1314 // replace
1315 keytrans[f].dst = ksdst;
1316 return;
1320 if (keytrans_used >= keytrans_size) {
1321 int newsize = keytrans_size+64;
1322 KeyTransDef *n = realloc(keytrans, sizeof(KeyTransDef)*newsize);
1324 if (n == NULL) die("out of memory");
1325 keytrans_size = newsize;
1326 keytrans = n;
1328 keytrans[keytrans_used].src = kssrc;
1329 keytrans[keytrans_used].dst = ksdst;
1330 ++keytrans_used;
1334 ////////////////////////////////////////////////////////////////////////////////
1335 static void parsekeyname (const char *str, KeySym *ks, uint *mask, int *kp) {
1336 char *s = alloca(strlen(str)+1);
1338 if (s == NULL) die("out of memory");
1339 strcpy(s, str);
1340 *kp = 0;
1341 *ks = NoSymbol;
1342 *mask = XK_NO_MOD;
1344 while (*s) {
1345 char *e, oc;
1346 uint mm = 0;
1347 int mod = 1;
1349 while (*s && isspace(*s)) ++s;
1350 for (e = s; *e && !isspace(*e) && *e != '+'; ++e) ;
1351 oc = *e; *e = 0;
1353 if (strcasecmp(s, "alt") == 0) mm = Mod1Mask;
1354 else if (strcasecmp(s, "win") == 0) mm = Mod4Mask;
1355 else if (strcasecmp(s, "ctrl") == 0) mm = ControlMask;
1356 else if (strcasecmp(s, "shift") == 0) mm = ShiftMask;
1357 else if (strcasecmp(s, "any") == 0) mm = XK_NO_MOD; //!
1358 else if (strcasecmp(s, "kpad") == 0) *kp = 1;
1359 else {
1360 mod = 0;
1361 if ((*ks = XStringToKeysym(s)) == NoSymbol) break;
1362 //fprintf(stderr, "[%s]\n", s);
1365 *e = oc;
1366 s = e;
1367 while (*s && isspace(*s)) ++s;
1368 if (mod) {
1369 if (*s != '+') { *ks = NoSymbol; break; }
1370 ++s;
1371 if (mm != 0) {
1372 if (mm == XK_NO_MOD) *mask = XK_ANY_MOD;
1373 else if (*mask == XK_NO_MOD) *mask = mm;
1374 else if (*mask != XK_ANY_MOD) *mask |= mm;
1376 } else {
1377 if (*s) { *ks = NoSymbol; break; }
1380 if (*ks == NoSymbol) die("invalid key name: '%s'", str);
1381 //fprintf(stderr, "mask=0x%08x, kp=%d\n", *mask, *kp);
1385 ////////////////////////////////////////////////////////////////////////////////
1386 static void keybinds_reset (void) {
1387 if (keybinds) free(keybinds);
1388 keybinds = NULL;
1389 keybinds_size = 0;
1390 keybinds_used = 0;
1394 static void keybind_add (const char *key, const char *act) {
1395 KeySym ks;
1396 uint mask;
1397 int kp;
1399 parsekeyname(key, &ks, &mask, &kp);
1401 for (int f = 0; f < keybinds_used; ++f) {
1402 if (keybinds[f].key == ks && keybinds[f].mask == mask) {
1403 // replace or remove
1404 free(keybinds[f].str);
1405 if (act == NULL || !act[0]) {
1406 // remove
1407 for (int c = f+1; c < keybinds_used; ++c) keybinds[c-1] = keybinds[c];
1408 } else {
1409 // replace
1410 if ((keybinds[f].str = strdup(act)) == NULL) die("out of memory");
1412 return;
1416 if (keybinds_used >= keybinds_size) {
1417 int newsize = keybinds_size+64;
1418 KeyInfoDef *n = realloc(keybinds, sizeof(KeyInfoDef)*newsize);
1420 if (n == NULL) die("out of memory");
1421 keybinds_size = newsize;
1422 keybinds = n;
1424 keybinds[keybinds_used].key = ks;
1425 keybinds[keybinds_used].mask = mask;
1426 keybinds[keybinds_used].kp = 0;
1427 if ((keybinds[keybinds_used].str = strdup(act)) == NULL) die("out of memory");
1428 ++keybinds_used;
1432 ////////////////////////////////////////////////////////////////////////////////
1433 static void keymap_reset (void) {
1434 if (keymap) free(keymap);
1435 keymap = NULL;
1436 keymap_size = 0;
1437 keymap_used = 0;
1441 static void keymap_add (const char *key, const char *act) {
1442 KeySym ks;
1443 uint mask;
1444 int kp;
1446 parsekeyname(key, &ks, &mask, &kp);
1448 for (int f = 0; f < keymap_used; ++f) {
1449 if (keymap[f].key == ks && keymap[f].mask == mask && keymap[f].kp == kp) {
1450 // replace or remove
1451 free(keymap[f].str);
1452 if (act == NULL) {
1453 // remove
1454 for (int c = f+1; c < keymap_used; ++c) keymap[c-1] = keymap[c];
1455 } else {
1456 // replace
1457 if ((keymap[f].str = strdup(act)) == NULL) die("out of memory");
1459 return;
1463 if (keymap_used >= keymap_size) {
1464 int newsize = keymap_size+128;
1465 KeyInfoDef *n = realloc(keymap, sizeof(KeyInfoDef)*newsize);
1467 if (n == NULL) die("out of memory");
1468 keymap_size = newsize;
1469 keymap = n;
1471 keymap[keymap_used].key = ks;
1472 keymap[keymap_used].mask = mask;
1473 keymap[keymap_used].kp = kp;
1474 if ((keymap[keymap_used].str = strdup(act)) == NULL) die("out of memory");
1475 ++keymap_used;
1479 ////////////////////////////////////////////////////////////////////////////////
1480 // selection
1481 static inline void setWantRedraw (void) {
1482 if (term != NULL) {
1483 term->wantRedraw = 1;
1484 term->lastDrawTime = mclock_ticks(); //FIXME: avoid excess redraw?
1489 static void inline markDirty (int lineno, int flag) {
1490 if (term != NULL && lineno >= 0 && lineno < term->row) {
1491 term->dirty[lineno] |= flag;
1492 term->wantRedraw = 1;
1493 term->lastDrawTime = mclock_ticks(); //FIXME: avoid excess redraw?
1498 static void selinit (void) {
1499 term->sel.tclick1 = term->sel.tclick2 = mclock_ticks();
1500 term->sel.mode = 0;
1501 term->sel.bx = -1;
1502 term->sel.clip = NULL;
1503 term->sel.xtarget = XA_UTF8;
1504 //if (term->sel.xtarget == None) term->sel.xtarget = XA_STRING;
1508 static void selhide (void) {
1509 if (term->sel.bx != -1) {
1510 term->sel.mode = 0;
1511 term->sel.bx = -1;
1512 tfulldirt();
1517 static inline int selected (int x, int y) {
1518 if (term->sel.bx == -1) return 0;
1520 if (y >= term->row) y = 0-(y-term->row+1);
1521 if (term->sel.ey == y && term->sel.by == y) {
1522 int bx = MIN(term->sel.bx, term->sel.ex);
1523 int ex = MAX(term->sel.bx, term->sel.ex);
1525 return BETWEEN(x, bx, ex);
1528 return
1529 ((term->sel.b.y < y && y < term->sel.e.y) || (y == term->sel.e.y && x <= term->sel.e.x)) ||
1530 (y == term->sel.b.y && x >= term->sel.b.x && (x <= term->sel.e.x || term->sel.b.y != term->sel.e.y));
1534 static void getbuttoninfo (XEvent *e, int *b, int *x, int *y) {
1535 if (b != NULL) *b = e->xbutton.button;
1536 if (x != NULL) *x = X2COL(e->xbutton.x);
1537 if (y != NULL) *y = Y2ROW(e->xbutton.y);
1538 term->sel.b.x = (term->sel.by < term->sel.ey ? term->sel.bx : term->sel.ex);
1539 term->sel.b.y = MIN(term->sel.by, term->sel.ey);
1540 term->sel.e.x = (term->sel.by < term->sel.ey ? term->sel.ex : term->sel.bx);
1541 term->sel.e.y = MAX(term->sel.by, term->sel.ey);
1545 static void mousereport (XEvent *e) {
1546 int x = X2COL(e->xbutton.x);
1547 int y = Y2ROW(e->xbutton.y);
1548 int button = e->xbutton.button;
1549 int state = e->xbutton.state;
1550 //char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
1551 char buf[32], *p;
1552 char lastCh = 'M';
1553 int ss;
1555 if (term == NULL) return;
1556 if (x < 0 || x >= term->col || y < 0 || y >= term->row) return;
1558 #if 0
1559 case 1000: /* X11 xterm mouse reporting */
1560 case 1005: /* utf-8 mouse encoding */
1561 case 1006: /* sgr mouse encoding */
1562 case 1015: /* urxvt mouse encoding */
1563 #endif
1564 //sprintf(buf, "\x1b[M%c%c%c", 0, 32+x+1, 32+y+1);
1565 p = buf+sprintf(buf, "\x1b[M");
1566 /* from urxvt */
1567 if (e->xbutton.type == MotionNotify) {
1568 if (!IS_SET(MODE_MOUSEMOTION) || (x == term->mouseox && y == term->mouseoy)) return;
1569 button = term->mouseob+32;
1570 term->mouseox = x;
1571 term->mouseoy = y;
1572 } else if (e->xbutton.type == ButtonRelease || button == AnyButton) {
1573 if (term->mousemode != 1006) {
1574 button = 3; // 'release' flag
1575 } else {
1576 lastCh = 'm';
1577 button -= Button1;
1578 if (button >= 3) button += 64-3;
1580 } else {
1581 button -= Button1;
1582 if (button >= 3) button += 64-3;
1583 if (e->xbutton.type == ButtonPress) {
1584 term->mouseob = button;
1585 term->mouseox = x;
1586 term->mouseoy = y;
1589 ss = (state & ShiftMask ? 4 : 0)+(state & Mod4Mask ? 8 : 0)+(state & ControlMask ? 16 : 0);
1590 switch (term->mousemode) {
1591 case 1006: /* sgr */
1592 buf[2] = '<';
1593 p += sprintf(p, "%d;", button+ss);
1594 break;
1595 case 1015: /* urxvt */
1596 --p; // remove 'M'
1597 p += sprintf(p, "%d;", 32+button+ss);
1598 break;
1599 default:
1600 *p++ = 32+button+ss;
1601 break;
1603 // coords
1604 switch (term->mousemode) {
1605 case 1005: /* utf-8 */
1606 p += utf8encode(x+1, p);
1607 p += utf8encode(y+1, p);
1608 break;
1609 case 1006: /* sgr */
1610 p += sprintf(p, "%d;%d%c", x+1, y+1, lastCh);
1611 break;
1612 case 1015: /* urxvt */
1613 p += sprintf(p, "%d;%dM", x+1, y+1);
1614 break;
1615 default:
1616 p += sprintf(p, "%c%c", 32+x+1, 32+y+1);
1617 break;
1619 *p = 0;
1622 fprintf(stderr, "(%d)<", term->mousemode);
1623 for (const unsigned char *s = (const unsigned char *)buf; *s; ++s) {
1624 if (s[0] < 32) fprintf(stderr, "{%d}", s[0]); else fputc(s[0], stderr);
1626 fputs(">\n", stderr);
1629 ttywritestr(buf);
1633 static void xfixsel (void) {
1634 if (lastSelStr != NULL) {
1635 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
1636 XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, xw.win, CurrentTime);
1637 } else {
1638 if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) == xw.win) XSetSelectionOwner(xw.dpy, XA_PRIMARY, None, CurrentTime);
1639 if (XGetSelectionOwner(xw.dpy, XA_CLIPBOARD) == xw.win) XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, None, CurrentTime);
1641 XFlush(xw.dpy);
1645 static void xsetsel (char *str) {
1646 /* register the selection for both the clipboard and the primary */
1647 if (term == NULL) return;
1648 if (term->sel.clip != NULL) free(term->sel.clip);
1649 term->sel.clip = str;
1650 if (lastSelStr != NULL) free(lastSelStr);
1651 lastSelStr = (str != NULL ? strdup(str) : NULL);
1652 xfixsel();
1653 //fprintf(stderr, "[%s]\n", str);
1657 static void selclear (XEvent *e) {
1658 if (lastSelStr != NULL) free(lastSelStr);
1659 lastSelStr = NULL;
1660 if (term != NULL) {
1661 if (term->sel.clip != NULL) free(term->sel.clip);
1662 term->sel.clip = NULL;
1663 term->sel.mode = 0;
1664 if (term->sel.bx != 0) {
1665 term->sel.bx = -1;
1666 tfulldirt();
1667 draw(1);
1673 static Line selgetlinebyy (int y) {
1674 Line l;
1676 if (y >= term->row) return NULL;
1677 if (y < 0) {
1678 if (y < -(term->maxhistory)) return NULL;
1679 l = term->line[term->row+(-y)-1];
1680 } else {
1681 l = term->line[y];
1683 return l;
1687 static void selcopy (void) {
1688 char *str, *ptr;
1689 int x, y, bufsize, is_selected = 0;
1691 if (term == NULL || term->sel.bx == -1) {
1692 str = NULL;
1693 } else {
1694 int sy = MIN(term->sel.e.y, term->sel.b.y);
1695 int ey = MAX(term->sel.e.y, term->sel.b.y);
1698 fprintf(stderr, "bx=%d; ex=%d; by=%d; ey=%d\n", term->sel.bx, term->sel.by, term->sel.ex, term->sel.ey);
1699 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);
1700 fprintf(stderr, " sy=%d; ey=%d\n", sy, ey);
1702 if (ey >= term->row) { selclear(NULL); return; }
1703 bufsize = (term->col+1)*(ey-sy+1)*UTF_SIZ;
1704 ptr = str = malloc(bufsize);
1705 /* append every set & selected glyph to the selection */
1706 for (y = sy; y <= ey; ++y) {
1707 Line l = selgetlinebyy(y);
1708 char *pstart = ptr;
1710 if (l == NULL) continue;
1711 for (x = 0; x < term->col; ++x) {
1712 if ((is_selected = selected(x, y)) != 0) {
1713 int size = utf8size(l[x].c);
1715 //if (size == 1) fprintf(stderr, "x=%d; y=%d; size=%d; c=%d\n", x, y, size, l[x].c[0]);
1716 if (size == 1) {
1717 unsigned char c = (unsigned char)l[x].c[0];
1719 *ptr = (c < 32 || c >= 127) ? ' ' : c;
1720 } else {
1721 memcpy(ptr, l[x].c, size);
1723 ptr += size;
1726 //fprintf(stderr, "y=%d; linebytes=%d\n", y, (int)(ptr-pstart));
1727 // trim trailing spaces
1728 while (ptr > pstart && ptr[-1] == ' ') --ptr;
1729 // \n at the end of every unwrapped selected line except for the last one
1730 if (is_selected && y < ey && selected(term->col-1, y) && !(l[term->col-1].state&GLYPH_WRAP)) *ptr++ = '\n';
1732 *ptr = 0;
1734 xsetsel(str);
1735 if (!str || !str[0]) selclear(NULL);
1739 static void selnotify (XEvent *e) {
1740 ulong nitems, ofs, rem;
1741 int format;
1742 uchar *data;
1743 Atom type;
1744 XSelectionEvent *se = (XSelectionEvent *)e;
1745 int isutf8;
1746 int wasbrk = 0;
1747 char *ucbuf = NULL;
1748 int ucbufsize = 0;
1750 if (term == NULL || term->cmdMode == CMDMODE_MESSAGE) return;
1751 #ifdef PASTE_SELECTION_DEBUG
1753 char *name;
1755 fprintf(stderr, "selnotify!\n");
1757 name = se->selection != None ? XGetAtomName(se->display, se->selection) : NULL;
1758 fprintf(stderr, " selection: [%s]\n", name);
1759 if (name != NULL) XFree(name);
1761 name = se->target != None ? XGetAtomName(se->display, se->target) : NULL;
1762 fprintf(stderr, " target: [%s]\n", name);
1763 if (name != NULL) XFree(name);
1765 name = se->property != None ? XGetAtomName(se->display, se->property) : NULL;
1766 fprintf(stderr, " property: [%s]\n", name);
1767 if (name != NULL) XFree(name);
1769 #endif
1771 if (se->property != XA_VT_SELECTION) return;
1773 #ifdef PASTE_SELECTION_DEBUG
1775 fprintf(stderr, "selection:\n");
1776 fprintf(stderr, " primary: %d\n", se->selection == XA_PRIMARY);
1777 fprintf(stderr, " secondary: %d\n", se->selection == XA_SECONDARY);
1778 fprintf(stderr, " clipboard: %d\n", se->selection == XA_CLIPBOARD);
1779 fprintf(stderr, " vtsel: %d\n", se->selection == XA_VT_SELECTION);
1780 fprintf(stderr, "target:\n");
1781 fprintf(stderr, " primary: %d\n", se->target == XA_PRIMARY);
1782 fprintf(stderr, " secondary: %d\n", se->target == XA_SECONDARY);
1783 fprintf(stderr, " clipboard: %d\n", se->target == XA_CLIPBOARD);
1784 fprintf(stderr, " vtsel: %d\n", se->target == XA_VT_SELECTION);
1786 #endif
1787 if (se->target == XA_UTF8) {
1788 isutf8 = 1;
1789 } else if (se->target == XA_STRING) {
1790 isutf8 = 0;
1791 } else if (se->target == XA_TARGETS) {
1792 Atom rqtype = None, *targ;
1794 if (XGetWindowProperty(xw.dpy, xw.win, se->property, 0, 65536, False, XA_ATOM, &type, &format, &nitems, &rem, &data)) {
1795 //fprintf(stderr, "no targets\n");
1796 rqtype = XA_STRING;
1797 } else {
1798 for (targ = (Atom *)data; nitems > 0; --nitems, ++targ) {
1799 #ifdef PASTE_SELECTION_DEBUG
1800 fprintf(stderr, " TGT: [%s]\n", XGetAtomName(se->display, *targ));
1801 #endif
1802 if (*targ == XA_UTF8) rqtype = XA_UTF8;
1803 else if (*targ == XA_STRING && rqtype == None) rqtype = XA_STRING;
1805 XFree(data);
1807 if (rqtype != None) XConvertSelection(xw.dpy, se->selection, rqtype, XA_VT_SELECTION, xw.win, CurrentTime);
1808 return;
1809 } else {
1810 return;
1813 ofs = 0;
1814 do {
1815 int blen;
1816 char *str;
1818 if (XGetWindowProperty(xw.dpy, xw.win, se->property, ofs, BUFSIZ/4, False, AnyPropertyType, &type, &format, &nitems, &rem, &data)) {
1819 fprintf(stderr, "Clipboard allocation failed\n");
1820 break;
1822 //fprintf(stderr, "nitems=%d; format=%d; rem=%d\n", (int)nitems, format, (int)rem);
1823 blen = nitems*format/8;
1825 if (!isutf8) {
1826 int newsz = blen*4+64;
1828 if (ucbufsize < newsz) {
1829 char *n = realloc(ucbuf, newsz);
1831 if (n == NULL) { XFree(data); break; }
1832 ucbuf = n;
1833 ucbufsize = newsz;
1836 blen = loc2utf(ucbuf, (const char *)data, blen);
1837 str = ucbuf;
1838 } else {
1839 str = (char *)data;
1842 if (term->cmdMode != CMDMODE_NONE) {
1843 tcmdput(str, blen);
1844 } else {
1845 if (nitems*format/8 > 0 && !wasbrk && IS_SET(MODE_BRACPASTE)) {
1846 wasbrk = 1;
1847 ttywritestr("\x1b[200~");
1849 ttywrite(str, blen);
1851 XFree(data);
1852 /* number of 32-bit chunks returned */
1853 ofs += nitems*format/32;
1854 } while (rem > 0);
1856 if (wasbrk) ttywritestr("\x1b[201~");
1857 if (ucbuf != NULL) free(ucbuf);
1861 static void selpaste (Atom which) {
1862 if (term == NULL) return;
1863 if (XGetSelectionOwner(xw.dpy, which) == None) return;
1864 //XConvertSelection(xw.dpy, which, term->sel.xtarget, XA_VT_SELECTION, xw.win, CurrentTime);
1865 XConvertSelection(xw.dpy, which, XA_TARGETS, XA_VT_SELECTION, xw.win, CurrentTime);
1867 if (which == XA_PRIMARY) selpaste(XA_SECONDARY);
1868 else if (which == XA_SECONDARY) selpaste(XA_CLIPBOARD);
1873 static void selrequest (XEvent *e) {
1874 XSelectionRequestEvent *xsre;
1875 XSelectionEvent xev;
1877 if (lastSelStr == NULL) return;
1878 xsre = (XSelectionRequestEvent *)e;
1879 xev.type = SelectionNotify;
1880 xev.requestor = xsre->requestor;
1881 xev.selection = xsre->selection;
1882 xev.target = xsre->target;
1883 xev.time = xsre->time;
1884 /* reject */
1885 xev.property = None;
1886 if (xsre->target == XA_TARGETS) {
1887 /* respond with the supported type */
1888 Atom tlist[3] = {XA_UTF8, XA_STRING, XA_TARGETS};
1890 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_ATOM, 32, PropModeReplace, (uchar *)tlist, 3);
1891 xev.property = xsre->property;
1892 } else if (xsre->target == XA_UTF8 && lastSelStr != NULL) {
1893 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_UTF8, 8, PropModeReplace, (uchar *)lastSelStr, strlen(lastSelStr));
1894 xev.property = xsre->property;
1895 } else if (xsre->target == XA_STRING && lastSelStr != NULL) {
1896 char *s = malloc(strlen(lastSelStr)*4+8);
1898 if (s != NULL) {
1899 int len = utf2loc(s, lastSelStr, strlen(lastSelStr));
1901 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_STRING, 8, PropModeReplace, (uchar *)s, len);
1902 xev.property = xsre->property;
1903 free(s);
1906 /* all done, send a notification to the listener */
1907 if (!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *)&xev)) fprintf(stderr, "Error sending SelectionNotify event\n");
1911 static int x2tab (int x) {
1912 if (x >= 0 && x < xw.w && xw.tabheight > 0) {
1913 x /= (xw.w/opt_tabcount)+firstVisibleTab;
1914 return (x >= 0 && x < term_count) ? x : -1;
1916 return -1;
1920 static void msTabSwitch (XEvent *e) {
1921 int tabn = x2tab(e->xbutton.x)+firstVisibleTab;
1923 if (tabn >= 0 && tabn != termidx) switchToTerm(tabn, 1);
1927 static void msTabScrollLeft (void) {
1928 if (firstVisibleTab > 0) {
1929 --firstVisibleTab;
1930 updateTabBar = 1;
1931 xdrawTabBar();
1936 static void msTabScrollRight (void) {
1937 int newidx = firstVisibleTab+1;
1939 if (newidx > term_count-opt_tabcount) return;
1940 firstVisibleTab = newidx;
1941 updateTabBar = 1;
1942 xdrawTabBar();
1946 static void bpress (XEvent *e) {
1947 if (term == NULL) return;
1949 if (xw.tabheight > 0) {
1950 if ((opt_tabposition == 0 && e->xbutton.y >= xw.h-xw.tabheight) ||
1951 (opt_tabposition != 0 && e->xbutton.y < xw.tabheight)) {
1952 switch (e->xbutton.button) {
1953 case Button1: // left
1954 msTabSwitch(e);
1955 break;
1956 case Button4: // wheel up
1957 msTabScrollLeft();
1958 break;
1959 case Button5: // wheel down
1960 msTabScrollRight();
1961 break;
1963 return;
1967 if ((e->xbutton.state&ShiftMask) != 0) {
1968 if (e->xbutton.button == Button1) {
1969 if (term->sel.bx != -1) tsetdirt(term->sel.b.y, term->sel.e.y);
1970 term->sel.mode = 1;
1971 term->sel.b.y = term->sel.e.y = term->row+1;
1972 term->sel.ex = term->sel.bx = X2COL(e->xbutton.x);
1973 term->sel.ey = term->sel.by = Y2ROW(e->xbutton.y);
1974 //fprintf(stderr, "x=%d; y=%d\n", term->sel.bx, term->sel.by);
1975 draw(1);
1976 return;
1979 if (e->xbutton.button == Button3) {
1980 term->sel.bx = -1;
1981 selcopy();
1982 draw(1);
1985 return;
1987 if (IS_SET(MODE_MOUSE)) mousereport(e);
1991 static void brelease (XEvent *e) {
1992 if (term == NULL) return;
1994 switch (opt_tabposition) {
1995 case 0: // bottom
1996 if (e->xbutton.y >= xw.h-xw.tabheight) return;
1997 break;
1998 case 1: // top
1999 if (e->xbutton.y < xw.tabheight) return;
2000 break;
2003 if ((e->xbutton.state&ShiftMask) == 0 && !term->sel.mode) {
2004 if (IS_SET(MODE_MOUSE)) mousereport(e);
2005 return;
2008 if (e->xbutton.button == Button2) {
2009 selpaste(XA_PRIMARY);
2010 } else if (e->xbutton.button == Button1) {
2011 term->sel.mode = 0;
2012 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey); // this sets sel.b and sel.e
2014 if (term->sel.bx == term->sel.ex && term->sel.by == term->sel.ey) {
2015 // single line, single char selection
2016 int now;
2018 markDirty(term->sel.ey, 2);
2019 term->sel.bx = -1;
2020 now = mclock_ticks();
2021 if (now-term->sel.tclick2 <= opt_tripleclick_timeout) {
2022 /* triple click on the line */
2023 term->sel.b.x = term->sel.bx = 0;
2024 term->sel.e.x = term->sel.ex = term->col;
2025 term->sel.b.y = term->sel.e.y = term->sel.ey;
2026 } else if (now-term->sel.tclick1 <= opt_doubleclick_timeout) {
2027 /* double click to select word */
2028 Line l = selgetlinebyy(term->sel.ey);
2030 if (l != NULL) {
2031 //FIXME: write better word selection code
2032 term->sel.bx = term->sel.ex;
2033 if (IS_GFX(l[term->sel.bx].attr)) {
2034 while (term->sel.bx > 0 && IS_GFX(l[term->sel.bx-1].attr)) --term->sel.bx;
2035 term->sel.b.x = term->sel.bx;
2036 while (term->sel.ex < term->col-1 && IS_GFX(l[term->sel.ex+1].attr)) ++term->sel.ex;
2037 } else {
2038 while (term->sel.bx > 0 && !IS_GFX(l[term->sel.bx-1].attr) && l[term->sel.bx-1].c[0] != ' ') --term->sel.bx;
2039 term->sel.b.x = term->sel.bx;
2040 while (term->sel.ex < term->col-1 && !IS_GFX(l[term->sel.ex+1].attr) && l[term->sel.ex+1].c[0] != ' ') ++term->sel.ex;
2042 term->sel.e.x = term->sel.ex;
2043 term->sel.b.y = term->sel.e.y = term->sel.ey;
2047 selcopy();
2048 draw(1);
2049 } else {
2050 // multiline or multichar selection
2051 selcopy();
2054 term->sel.tclick2 = term->sel.tclick1;
2055 term->sel.tclick1 = mclock_ticks();
2056 //draw(1);
2060 static void bmotion (XEvent *e) {
2061 if (term == NULL) return;
2063 switch (opt_tabposition) {
2064 case 0: // bottom
2065 if (e->xbutton.y >= xw.h-xw.tabheight) return;
2066 break;
2067 case 1: // top
2068 if (e->xbutton.y < xw.tabheight) return;
2069 break;
2072 if (term->sel.mode) {
2073 int oldey = term->sel.ey, oldex = term->sel.ex;
2075 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey); // this sets sel.b and sel.e
2076 if (oldey != term->sel.ey || oldex != term->sel.ex) {
2077 int starty = MIN(oldey, term->sel.ey);
2078 int endy = MAX(oldey, term->sel.ey);
2080 tsetdirt(starty, endy);
2081 draw(1);
2083 return;
2085 //if (IS_SET(MODE_MOUSE) && e->xbutton.button != 0) mousereport(e);
2089 ////////////////////////////////////////////////////////////////////////////////
2090 // tty init
2092 static void dump (char c) {
2093 static int col;
2095 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
2096 if (++col % 10 == 0) fprintf(stderr, "\n");
2101 static __attribute__((noreturn)) void execsh (const char *str) {
2102 char **args;
2104 if (str == NULL) {
2105 char *envshell = getenv("SHELL");
2107 DEFAULT(envshell, opt_shell);
2108 setenv("TERM", opt_term, 1);
2109 args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL};
2110 } else {
2111 int argc = 0;
2113 args = calloc(32768, sizeof(char *));
2114 if (args == NULL) exit(EXIT_FAILURE);
2115 while (*str) {
2116 const char *b;
2118 while (*str && isspace(*str)) ++str;
2119 if (!str[0]) break;
2121 b = str;
2122 while (*str && !isspace(*str)) {
2123 if (*str++ == '\\') {
2124 if (*str) ++str;
2128 args[argc] = calloc(str-b+1, 1);
2129 memcpy(args[argc], b, str-b);
2132 FILE *fo = fopen("z.log", "a");
2133 fprintf(fo, "%d: [%s]\n", argc, args[argc]);
2134 fclose(fo);
2137 ++argc;
2139 if (argc < 1) exit(EXIT_FAILURE);
2141 execvp(args[0], args);
2142 exit(EXIT_FAILURE);
2146 static int ttynew (Term *term) {
2147 int m, s;
2148 struct winsize w = {term->row, term->col, 0, 0};
2149 static int signalset = 0;
2151 if (openpty(&m, &s, NULL, NULL, &w) < 0) die("openpty failed: %s", SERRNO);
2152 term->cmdfd = m;
2153 ttyresize();
2154 term->cmdfd = -1;
2155 switch (term->pid = fork()) {
2156 case -1: /* error */
2157 fprintf(stderr, "fork failed");
2158 return -1;
2159 case 0: /* child */
2160 setsid(); /* create a new process group */
2161 dup2(s, STDIN_FILENO);
2162 dup2(s, STDOUT_FILENO);
2163 dup2(s, STDERR_FILENO);
2164 if (ioctl(s, TIOCSCTTY, NULL) < 0) die("ioctl TIOCSCTTY failed: %s", SERRNO);
2165 close(s);
2166 close(m);
2167 execsh(term->execcmd);
2168 break;
2169 default: /* master */
2170 close(s);
2171 term->cmdfd = m;
2172 term->dead = 0;
2173 ttyresize();
2174 if (!signalset) { signalset = 1; signal(SIGCHLD, sigchld); }
2175 break;
2177 return 0;
2181 ////////////////////////////////////////////////////////////////////////////////
2182 // tty r/w
2183 static int ttycanread (void) {
2184 for (;;) {
2185 fd_set rfd;
2186 struct timeval timeout = {0};
2188 if (term->dead || term->cmdfd < 0) return 0;
2189 FD_ZERO(&rfd);
2190 FD_SET(term->cmdfd, &rfd);
2191 if (select(term->cmdfd+1, &rfd, NULL, NULL, &timeout) < 0) {
2192 if (errno == EINTR) continue;
2193 die("select failed: %s", SERRNO);
2195 if (FD_ISSET(term->cmdfd, &rfd)) return 1;
2196 break;
2198 return 0;
2202 static int ttycanwrite (void) {
2203 for (;;) {
2204 fd_set wfd;
2205 struct timeval timeout = {0};
2207 if (term->dead || term->cmdfd < 0) return 0;
2208 FD_ZERO(&wfd);
2209 FD_SET(term->cmdfd, &wfd);
2210 if (select(term->cmdfd+1, NULL, &wfd, NULL, &timeout) < 0) {
2211 if (errno == EINTR) continue;
2212 die("select failed: %s", SERRNO);
2214 if (FD_ISSET(term->cmdfd, &wfd)) return 1;
2215 break;
2217 return 0;
2221 #ifdef DUMP_IO
2222 static void wrstr (const char *s, int len) {
2223 if (s == NULL) return;
2224 while (len-- > 0) {
2225 unsigned char c = (unsigned char)(*s++);
2227 if (c < 32) fprintf(stderr, "{%u}", c); else fwrite(&c, 1, 1, stderr);
2230 #endif
2233 static void ttyread (void) {
2234 char *ptr;
2235 int left;
2237 /* append read bytes to unprocessed bytes */
2238 if (term == NULL || term->dead || term->cmdfd < 0) return;
2239 #ifdef DUMP_PROG_OUTPUT
2240 term->xobuflen = term->obuflen;
2241 #endif
2242 left = OBUFSIZ-term->obuflen;
2243 //dlogf("0: ttyread before: free=%d, used=%d", left, term->obuflen);
2244 while (left > 0 && ttycanread()) {
2245 int ret;
2247 //if ((ret = read(term->cmdfd, term->obuf+term->obuflen, 1)) < 0) die("Couldn't read from shell: %s", SERRNO);
2248 if ((ret = read(term->cmdfd, term->obuf+term->obuflen, left)) < 0) {
2249 //fprintf(stderr, "Warning: couldn't read from tty #%d: %s\n", termidx, SERRNO);
2250 break;
2252 term->obuflen += ret;
2253 left -= ret;
2255 //dlogf("1: ttyread after: free=%d, used=%d", left, term->obuflen);
2256 /* process every complete utf8 char */
2257 #ifdef DUMP_PROG_OUTPUT
2259 FILE *fo = fopen("zlogo.log", "ab");
2260 if (fo) {
2261 fwrite(term->obuf+term->xobuflen, term->obuflen-term->xobuflen, 1, fo);
2262 fclose(fo);
2265 #endif
2266 ptr = term->obuf;
2267 if (term->needConv) {
2268 // need conversion from locale to utf-8
2269 //fprintf(stderr, "buf: %d bytes\n", term->obuflen);
2270 while (term->obuflen > 0) {
2271 char obuf[UTF_SIZ+1];
2272 int len;
2274 len = loc2utf(obuf, ptr, 1);
2275 #ifdef DUMP_IO_READ
2277 fprintf(stderr, "rdc: [");
2278 wrstr(ptr, 1);
2279 fprintf(stderr, "] --> [");
2280 wrstr(obuf, len);
2281 fprintf(stderr, "]\n");
2282 fflush(stderr);
2284 #endif
2285 if (len > 0) {
2286 obuf[len] = 0;
2287 tputc(obuf);
2289 ++ptr;
2290 --term->obuflen;
2292 term->obuflen = 0;
2293 } else {
2294 // don't do any conversion
2295 while (term->obuflen >= UTF_SIZ || isfullutf8(ptr, term->obuflen)) {
2296 ulong utf8c;
2297 char s[UTF_SIZ+1];
2298 int charsize = utf8decode(ptr, &utf8c); /* returns size of utf8 char in bytes */
2299 int len;
2301 len = utf8encode(utf8c, s);
2302 #ifdef DUMP_IO_READ
2304 fprintf(stderr, "rdx: [");
2305 wrstr(s, len);
2306 fprintf(stderr, "]\n");
2307 fflush(stderr);
2309 #endif
2310 if (len > 0) {
2311 s[len] = 0;
2312 tputc(s);
2314 ptr += charsize;
2315 term->obuflen -= charsize;
2317 //dlogf("2: ttyread afterproc: used=%d", term->obuflen);
2319 /* keep any uncomplete utf8 char for the next call */
2320 if (term->obuflen > 0) memmove(term->obuf, ptr, term->obuflen);
2324 static void ttyflushwrbuf (void) {
2325 if (term == NULL || term->dead || term->cmdfd < 0) return;
2326 if (term->wrbufpos >= term->wrbufused) {
2327 term->wrbufpos = term->wrbufused = 0;
2328 return;
2330 //dlogf("0: ttyflushwrbuf before: towrite=%d", term->wrbufused-term->wrbufpos);
2331 while (term->wrbufpos < term->wrbufused && ttycanwrite()) {
2332 int ret;
2334 if ((ret = write(term->cmdfd, term->wrbuf+term->wrbufpos, term->wrbufused-term->wrbufpos)) == -1) {
2335 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2337 term->wrbufpos += ret;
2339 if (term->wrbufpos > 0) {
2340 int left = term->wrbufused-term->wrbufpos;
2342 if (left < 1) {
2343 // write buffer is empty
2344 term->wrbufpos = term->wrbufused = 0;
2345 } else {
2346 memmove(term->wrbuf, term->wrbuf+term->wrbufpos, left);
2347 term->wrbufpos = 0;
2348 term->wrbufused = left;
2351 //dlogf("1: ttyflushwrbuf after: towrite=%d", term->wrbufused-term->wrbufpos);
2355 // convert char to locale and write it
2356 static void ttywriterawchar (const char *s, int len) {
2357 char loc[16];
2358 int clen;
2360 if (s == NULL || len < 1) return;
2361 if (term->needConv) {
2362 if ((clen = utf2loc(loc, s, len)) < 1) return;
2363 } else {
2364 if ((clen = utf8size(s)) < 1) return;
2365 memmove(loc, s, clen);
2367 #ifdef DUMP_IO_WRITE
2369 fprintf(stderr, "wrc: [");
2370 wrstr(s, len);
2371 fprintf(stderr, "] --> [");
2372 wrstr(loc, clen);
2373 fprintf(stderr, "]\n");
2374 fflush(stderr);
2376 #endif
2378 while (term->wrbufused+clen >= term->wrbufsize) {
2379 //FIXME: make write buffer dynamic?
2380 // force write at least one char
2381 //dlogf("ttywrite: forced write");
2382 if (write(term->cmdfd, term->wrbuf+term->wrbufpos, 1) == -1) {
2383 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2384 } else {
2385 ++term->wrbufpos;
2387 ttyflushwrbuf(); // make room for char
2389 memcpy(term->wrbuf+term->wrbufused, loc, clen);
2390 term->wrbufused += clen;
2394 static void ttywrite (const char *s, size_t n) {
2395 if (term == NULL || term->dead || term->cmdfd < 0) return;
2396 #ifdef DUMP_PROG_INPUT
2397 if (s != NULL && n > 0) {
2398 FILE *fo = fopen("zlogw.log", "ab");
2399 if (fo) {
2400 fwrite(s, n, 1, fo);
2401 fclose(fo);
2404 #endif
2405 //ttyflushwrbuf();
2406 if (s != NULL && n > 0) {
2407 while (n > 0) {
2408 unsigned char c = (unsigned char)(s[0]);
2410 if (term->ubufpos > 0 && isfullutf8(term->ubuf, term->ubufpos)) {
2411 // have complete char
2412 ttywriterawchar(term->ubuf, term->ubufpos);
2413 term->ubufpos = 0;
2414 continue;
2417 if (term->ubufpos == 0) {
2418 // new char
2419 if (c < 128) {
2420 ttywriterawchar(s, 1);
2421 } else if ((c&0xc0) == 0xc0) {
2422 // new utf-8 char
2423 term->ubuf[term->ubufpos++] = *s;
2424 } else {
2425 // ignore unsynced utf-8
2427 ++s;
2428 --n;
2429 continue;
2431 // char continues
2432 if (c < 128 || term->ubufpos >= UTF_SIZ || (c&0xc0) == 0xc0) {
2433 // discard previous utf-8, it's bad
2434 term->ubufpos = 0;
2435 continue;
2437 // collect
2438 term->ubuf[term->ubufpos++] = *s;
2439 ++s;
2440 --n;
2441 if (isfullutf8(term->ubuf, term->ubufpos)) {
2442 // have complete char
2443 ttywriterawchar(term->ubuf, term->ubufpos);
2444 term->ubufpos = 0;
2448 ttyflushwrbuf();
2452 ////////////////////////////////////////////////////////////////////////////////
2453 // tty resize ioctl
2454 static void ttyresize (void) {
2455 struct winsize w;
2457 if (term != NULL && term->cmdfd >= 0) {
2458 w.ws_row = term->row;
2459 w.ws_col = term->col;
2460 w.ws_xpixel = w.ws_ypixel = 0;
2461 if (ioctl(term->cmdfd, TIOCSWINSZ, &w) < 0) fprintf(stderr, "Warning: couldn't set window size: %s\n", SERRNO);
2462 setWantRedraw();
2467 ////////////////////////////////////////////////////////////////////////////////
2468 // tty utilities
2469 static void csidump (void) {
2470 printf("ESC");
2471 for (int f = 1; f < term->escseq.len; ++f) {
2472 uint c = (term->escseq.buf[f]&0xff);
2474 if (isprint(c)) putchar(c);
2475 else if (c == '\n') printf("(\\n)");
2476 else if (c == '\r') printf("(\\r)");
2477 else if (c == 0x1b) printf("(\\e)");
2478 else printf("(%02x)", c);
2480 putchar('\n');
2484 static void tsetdirt (int top, int bot) {
2485 LIMIT(top, 0, term->row-1);
2486 LIMIT(bot, 0, term->row-1);
2487 for (int y = top; y <= bot; ++y) markDirty(y, 2);
2491 static void tfulldirt (void) {
2492 tsetdirt(0, term->row-1);
2496 static void tmoveto (int x, int y) {
2497 LIMIT(x, 0, term->col-1);
2498 LIMIT(y, 0, term->row-1);
2499 term->c.state &= ~CURSOR_WRAPNEXT;
2500 if (term->c.x != x || term->c.y != y) {
2501 term->c.x = x;
2502 term->c.y = y;
2503 setWantRedraw();
2508 static void tclearregion (int x1, int y1, int x2, int y2) {
2509 int temp;
2511 //fprintf(stderr, "tclearregion: (%d,%d)-(%d,%d)\n", x1, y1, x2, y2);
2512 if (x1 > x2) { temp = x1; x1 = x2; x2 = temp; }
2513 if (y1 > y2) { temp = y1; y1 = y2; y2 = temp; }
2514 LIMIT(x1, 0, term->col-1);
2515 LIMIT(x2, 0, term->col-1);
2516 LIMIT(y1, 0, term->row-1);
2517 LIMIT(y2, 0, term->row-1);
2518 //fprintf(stderr, " tclearregion: (%d,%d)-(%d,%d)\n", x1, y1, x2, y2);
2519 for (int y = y1; y <= y2; ++y) {
2520 Line l = term->line[y];
2522 markDirty(y, (x1 <= 0 && x2 >= term->col-1) ? 2 : 1);
2523 for (int x = x1; x <= x2; ++x) {
2524 l[x].fg = term->c.attr.fg;
2525 l[x].bg = term->c.attr.bg;
2526 l[x].state = GLYPH_DIRTY;
2527 l[x].attr = ATTR_NULL|(term->c.attr.attr&(ATTR_DEFFG|ATTR_DEFBG));
2528 l[x].c[0] = ' ';
2529 if (term->sel.bx != -1 && selected(x, y)) selhide();
2531 l[term->col-1].state &= ~GLYPH_WRAP;
2536 static void tcursor (int mode) {
2537 if (mode == CURSOR_SAVE) {
2538 term->csaved = term->c;
2539 } else if (mode == CURSOR_LOAD) {
2540 term->c = term->csaved;
2541 tmoveto(term->c.x, term->c.y);
2542 setWantRedraw();
2547 static void treset (void) {
2548 Glyph g;
2550 term->c = (TCursor){{
2551 .attr = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG,
2552 .fg = 0,
2553 .bg = 0
2554 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
2555 term->c.attr.fg = term->deffg;
2556 term->c.attr.bg = term->defbg;
2558 g.state = GLYPH_DIRTY;
2559 g.attr = term->c.attr.attr;
2560 g.fg = term->c.attr.fg;
2561 g.bg = term->c.attr.bg;
2562 g.c[0] = ' ';
2563 g.c[1] = 0;
2565 term->top = 0;
2566 term->bot = term->row-1;
2567 term->mode = MODE_WRAP/* | MODE_MOUSEBTN*/;
2568 term->mousemode = 1000;
2569 term->charset = MODE_GFX0;
2570 //tclearregion(0, 0, term->col-1, term->row-1);
2571 for (int y = 0; y < term->row; ++y) {
2572 markDirty(y, 2);
2573 for (int x = 0; x < term->col; ++x) term->alt[y][x] = term->line[y][x] = g;
2575 for (int y = term->row; y < term->linecount; ++y) {
2576 for (int x = 0; x < term->col; ++x) term->line[y][x] = g;
2578 tcursor(CURSOR_SAVE);
2579 term->topline = 0;
2580 tfulldirt();
2584 static int tinitialize (int col, int row) {
2585 //memset(term, 0, sizeof(Term));
2586 //term->needConv = needConversion ? 1 : 0;
2587 term->wrbufsize = WBUFSIZ;
2588 term->deffg = term->deffg;
2589 term->defbg = term->defbg;
2590 term->row = row;
2591 term->col = col;
2592 term->dirty = calloc(term->row, sizeof(*term->dirty));
2593 term->maxhistory = opt_maxhistory;
2594 term->linecount = term->maxhistory+term->row;
2595 term->line = calloc(term->linecount, sizeof(Line));
2596 term->alt = calloc(term->row, sizeof(Line));
2597 for (int y = 0; y < term->linecount; ++y) term->line[y] = calloc(term->col, sizeof(Glyph));
2598 for (int y = 0; y < term->row; ++y) term->alt[y] = calloc(term->col, sizeof(Glyph));
2599 /* setup screen */
2600 treset();
2601 return 1;
2605 static void tadjustmaxhistory (int maxh) {
2606 if (term != NULL) {
2607 LIMIT(maxh, 0, 65535);
2608 if (term->maxhistory < maxh) {
2609 Line *nl;
2610 int newlc = term->linecount+(maxh-term->maxhistory);
2612 // add history lines
2613 if ((nl = realloc(term->line, sizeof(Line)*newlc)) != NULL) {
2614 Glyph g;
2616 term->topline = 0;
2617 term->line = nl;
2618 g.state = GLYPH_DIRTY;
2619 g.attr = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG;
2620 g.fg = term->deffg;
2621 g.bg = term->defbg;
2622 g.c[0] = ' ';
2623 g.c[1] = 0;
2624 for (int y = term->linecount; y < newlc; ++y) {
2625 term->line[y] = calloc(term->col, sizeof(Glyph));
2626 for (int x = 0; x < term->col; ++x) term->line[y][x] = g;
2628 term->maxhistory = maxh;
2629 term->linecount = newlc;
2631 } else if (term->maxhistory > maxh) {
2632 Line *nl;
2634 term->topline = 0;
2635 // remove history lines
2636 while (term->linecount > term->row+maxh) free(term->line[--term->linecount]);
2637 if ((nl = realloc(term->line, sizeof(Line)*term->linecount)) != NULL) term->line = nl;
2638 term->maxhistory = maxh;
2644 static void tswapscreen (void) {
2645 selhide();
2646 for (int f = 0; f < term->row; ++f) {
2647 Line t = term->line[f];
2649 term->line[f] = term->alt[f];
2650 term->alt[f] = t;
2652 term->mode ^= MODE_ALTSCREEN;
2653 tfulldirt();
2657 //FIXME: works bad with history
2658 //FIXME: ugly code
2659 static void selscroll (int orig, int n, int tohistory) {
2660 int docopy = 0;
2662 if (term->sel.bx == -1) return;
2664 tfulldirt(); // just in case
2665 if (!tohistory) {
2666 if (BETWEEN(term->sel.by, orig, term->bot) || BETWEEN(term->sel.ey, orig, term->bot)) {
2667 if ((term->sel.by += n) > term->bot || (term->sel.ey += n) < term->top) {
2668 selclear(NULL);
2669 return;
2671 if (term->sel.by < term->top) {
2672 term->sel.by = term->top;
2673 term->sel.bx = 0;
2674 docopy = 1;
2676 if (term->sel.ey > term->bot) {
2677 term->sel.ey = term->bot;
2678 term->sel.ex = term->col;
2679 docopy = 1;
2681 term->sel.b.x = term->sel.bx;
2682 term->sel.b.y = term->sel.by;
2683 term->sel.e.x = term->sel.ex;
2684 term->sel.e.y = term->sel.ey;
2686 } else {
2687 // tohistory!=0; always scrolls full screen up (n == -1)
2688 //fprintf(stderr, "selscroll to history\n");
2689 term->sel.by += n;
2690 term->sel.ey += n;
2691 //fprintf(stderr, " by=%d; ey=%d; maxhistory=%d\n", term->sel.by, term->sel.ey, term->maxhistory);
2692 if (term->sel.ey < 0 && -(term->sel.ey) > term->maxhistory) {
2693 // out of screen completely
2694 selclear(NULL);
2695 return;
2697 if (term->sel.by < 0 && -(term->sel.by) > term->maxhistory) {
2698 term->sel.by = -term->maxhistory;
2699 term->sel.bx = 0;
2700 docopy = 1;
2702 term->sel.b.x = term->sel.bx;
2703 term->sel.b.y = term->sel.by;
2704 term->sel.e.x = term->sel.ex;
2705 term->sel.e.y = term->sel.ey;
2708 if (docopy) selcopy();
2712 static void tscrolldown (int orig, int n) {
2713 Line temp;
2715 LIMIT(n, 0, term->bot-orig+1);
2716 if (n < 1) return;
2717 selscroll(orig, n, 0);
2718 //fprintf(stderr, "tscrolldown(%d, %d)\n", orig, n);
2719 tclearregion(0, term->bot-n+1, term->col-1, term->bot);
2720 for (int f = term->bot; f >= orig+n; --f) {
2721 temp = term->line[f];
2722 term->line[f] = term->line[f-n];
2723 term->line[f-n] = temp;
2724 markDirty(f, 2);
2725 markDirty(f-n, 2);
2730 static void tscrollup (int orig, int n, int tohistory) {
2731 Line temp;
2733 if (term == NULL) return;
2734 LIMIT(n, 0, term->bot-orig+1);
2735 if (n < 1) return;
2736 //fprintf(stderr, "tscrollup(%d, %d)\n", orig, n);
2737 if (tohistory && !IS_SET(MODE_ALTSCREEN) && term->maxhistory > 0) {
2738 Line l = term->line[term->linecount-1];
2740 for (int f = term->linecount-1; f > term->row; --f) term->line[f] = term->line[f-1];
2741 term->line[term->row] = l;
2742 for (int x = 0; x < term->col; ++x) l[x] = term->line[0][x];
2743 } else {
2744 tohistory = 0;
2747 selscroll(orig, -n, tohistory);
2748 //tclearregion(0, orig, term->col-1, orig+n-1);
2749 for (int f = orig; f <= term->bot-n; ++f) {
2750 temp = term->line[f];
2751 term->line[f] = term->line[f+n];
2752 term->line[f+n] = temp;
2753 markDirty(f, 2);
2754 markDirty(f+n, 2);
2756 tclearregion(0, term->bot-n+1, term->col-1, term->bot);
2760 static inline void tsetcharwrap (int y, int wrap) {
2761 if (y >= 0 && y < term->row) {
2762 if (wrap) term->line[y][term->col-1].state |= GLYPH_WRAP;
2763 else term->line[y][term->col-1].state &= ~GLYPH_WRAP;
2768 static void tnewline (int first_col) {
2769 int y = term->c.y;
2771 tsetcharwrap(y, (first_col == 2)); // 2: wrapping
2772 if (y == term->bot) tscrollup(term->top, 1, 1); else ++y;
2773 tmoveto(first_col ? 0 : term->c.x, y);
2777 static void csiparse (void) {
2778 const char *p = term->escseq.buf;
2780 term->escseq.narg = 0;
2781 if (*p == '?') { term->escseq.priv = 1; ++p; }
2782 while (p < term->escseq.buf+term->escseq.len) {
2783 int n = term->escseq.arg[term->escseq.narg];
2785 for (; *p && isdigit(*p); ++p) n = n*10+(p[0]-'0');
2786 term->escseq.arg[term->escseq.narg] = n;
2788 if (*p == ';' && term->escseq.narg+1 < ESC_ARG_SIZ) {
2789 ++term->escseq.narg;
2790 ++p;
2791 } else {
2792 term->escseq.mode = *p;
2793 ++term->escseq.narg;
2794 break;
2800 static void tsetchar (const char *c) {
2801 char ub[UTF_SIZ+1];
2802 int rev = 0, gfx = 0;
2803 int x = term->c.x, y = term->c.y;
2805 if (x < 0 || x >= term->col || y < 0 || y >= term->row) return;
2807 if (!term->needConv && unimap != NULL) {
2808 ulong cc;
2810 utf8decode(c, &cc);
2811 if (cc <= 65535) {
2812 ushort uc = unimap[cc];
2814 if (uc) {
2815 if (uc == 127) {
2816 // inversed space
2817 rev = 1;
2818 ub[0] = ' ';
2819 //ub[1] = 0;
2820 } else {
2821 if (uc&0x8000) {
2822 ub[0] = (uc&0x7f);
2823 gfx = 1;
2824 } else {
2825 //ub[0] = uc&0x7f;
2826 utf8encode(uc, ub);
2829 c = ub;
2833 markDirty(y, 1);
2835 term->line[y][x] = term->c.attr;
2836 if (rev) term->line[y][x].attr ^= ATTR_REVERSE;
2837 if (gfx || (term->mode&term->charset)) {
2838 term->line[y][x].attr |= ATTR_GFX;
2839 } else {
2840 term->line[y][x].attr &= ~ATTR_GFX;
2843 term->line[y][x].state = (GLYPH_SET | GLYPH_DIRTY);
2844 memmove(term->line[y][x].c, c, UTF_SIZ);
2846 if (IS_GFX(term->line[y][x].attr)) {
2847 unsigned char c = (unsigned char)(term->line[y][x].c[0]);
2849 if (c > 95 && c < 128) term->line[y][x].c[0] -= 95;
2850 else if (c > 127) term->line[y][x].c[0] = ' ';
2852 if (term->sel.bx != -1 && selected(x, y)) selhide();
2853 //dlogf("tsetchar(%d,%d): [%c] (%d); dirty=%d\n", x, y, c[0], term->wantRedraw, term->dirty[y]);
2857 static void tdeletechar (int n) {
2858 int src = term->c.x+n;
2859 int dst = term->c.x;
2860 int size = term->col-src;
2862 markDirty(term->c.y, 2);
2863 if (src >= term->col) {
2864 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2865 } else {
2866 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2867 tclearregion(term->col-n, term->c.y, term->col-1, term->c.y);
2872 static void tinsertblank (int n) {
2873 int src = term->c.x;
2874 int dst = src+n;
2875 int size = term->col-dst;
2877 markDirty(term->c.y, 2);
2878 if (dst >= term->col) {
2879 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2880 } else {
2881 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2882 tclearregion(src, term->c.y, dst-1, term->c.y);
2887 static void tinsertblankline (int n) {
2888 if (term->c.y < term->top || term->c.y > term->bot) return;
2889 tscrolldown(term->c.y, n);
2893 static void tdeleteline (int n) {
2894 if (term->c.y < term->top || term->c.y > term->bot) return;
2895 tscrollup(term->c.y, n, 0);
2899 static void tsetattr (int *attr, int l) {
2900 for (int f = 0; f < l; ++f) {
2901 switch (attr[f]) {
2902 case 0:
2903 term->c.attr.attr &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
2904 term->c.attr.attr |= ATTR_DEFFG | ATTR_DEFBG;
2905 term->c.attr.fg = term->deffg;
2906 term->c.attr.bg = term->defbg;
2907 break;
2908 case 1:
2909 term->c.attr.attr |= ATTR_BOLD;
2910 break;
2911 case 4:
2912 term->c.attr.attr |= ATTR_UNDERLINE;
2913 break;
2914 case 7:
2915 term->c.attr.attr |= ATTR_REVERSE;
2916 break;
2917 case 22:
2918 term->c.attr.attr &= ~ATTR_BOLD;
2919 break;
2920 case 24:
2921 term->c.attr.attr &= ~ATTR_UNDERLINE;
2922 break;
2923 case 27:
2924 term->c.attr.attr &= ~ATTR_REVERSE;
2925 break;
2926 case 38:
2927 if (f+2 < l && attr[f+1] == 5) {
2928 f += 2;
2929 if (BETWEEN(attr[f], 0, 255)) {
2930 term->c.attr.fg = attr[f];
2931 term->c.attr.attr &= ~ATTR_DEFFG;
2932 } else {
2933 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[f]);
2935 } else {
2936 //fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2937 term->c.attr.fg = term->deffg;
2938 term->c.attr.attr |= ATTR_DEFFG;
2940 break;
2941 case 39:
2942 term->c.attr.fg = term->deffg;
2943 term->c.attr.attr |= ATTR_DEFFG;
2944 break;
2945 case 48:
2946 if (f+2 < l && attr[f+1] == 5) {
2947 f += 2;
2948 if (BETWEEN(attr[f], 0, 255)) {
2949 term->c.attr.bg = attr[f];
2950 term->c.attr.attr &= ~ATTR_DEFBG;
2951 } else {
2952 fprintf(stderr, "erresc: bad bgcolor %d\n", attr[f]);
2954 } else {
2955 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2957 break;
2958 case 49:
2959 term->c.attr.bg = term->defbg;
2960 term->c.attr.attr |= ATTR_DEFBG;
2961 break;
2962 default:
2963 if (BETWEEN(attr[f], 30, 37)) { term->c.attr.fg = attr[f]-30; term->c.attr.attr &= ~ATTR_DEFFG; }
2964 else if (BETWEEN(attr[f], 40, 47)) { term->c.attr.bg = attr[f]-40; term->c.attr.attr &= ~ATTR_DEFBG; }
2965 else if (BETWEEN(attr[f], 90, 97)) { term->c.attr.fg = attr[f]-90+8; term->c.attr.attr &= ~ATTR_DEFFG; }
2966 else if (BETWEEN(attr[f], 100, 107)) { term->c.attr.bg = attr[f]-100+8; term->c.attr.attr &= ~ATTR_DEFBG; }
2967 else { fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]); csidump(); }
2968 break;
2974 static void tsetscroll (int t, int b) {
2975 int temp;
2977 LIMIT(t, 0, term->row-1);
2978 LIMIT(b, 0, term->row-1);
2979 if (t > b) {
2980 temp = t;
2981 t = b;
2982 b = temp;
2984 term->top = t;
2985 term->bot = b;
2989 ////////////////////////////////////////////////////////////////////////////////
2990 // esc processing
2991 static void csihandle (void) {
2992 switch (term->escseq.mode) {
2993 case '@': /* ICH -- Insert <n> blank char */
2994 DEFAULT(term->escseq.arg[0], 1);
2995 tinsertblank(term->escseq.arg[0]);
2996 break;
2997 case 'A': /* CUU -- Cursor <n> Up */
2998 case 'e':
2999 DEFAULT(term->escseq.arg[0], 1);
3000 tmoveto(term->c.x, term->c.y-term->escseq.arg[0]);
3001 break;
3002 case 'B': /* CUD -- Cursor <n> Down */
3003 DEFAULT(term->escseq.arg[0], 1);
3004 tmoveto(term->c.x, term->c.y+term->escseq.arg[0]);
3005 break;
3006 case 'C': /* CUF -- Cursor <n> Forward */
3007 case 'a':
3008 DEFAULT(term->escseq.arg[0], 1);
3009 tmoveto(term->c.x+term->escseq.arg[0], term->c.y);
3010 break;
3011 case 'D': /* CUB -- Cursor <n> Backward */
3012 DEFAULT(term->escseq.arg[0], 1);
3013 tmoveto(term->c.x-term->escseq.arg[0], term->c.y);
3014 break;
3015 case 'E': /* CNL -- Cursor <n> Down and first col */
3016 DEFAULT(term->escseq.arg[0], 1);
3017 tmoveto(0, term->c.y+term->escseq.arg[0]);
3018 break;
3019 case 'F': /* CPL -- Cursor <n> Up and first col */
3020 DEFAULT(term->escseq.arg[0], 1);
3021 tmoveto(0, term->c.y-term->escseq.arg[0]);
3022 break;
3023 case 'G': /* CHA -- Move to <col> */
3024 case '`': /* XXX: HPA -- same? */
3025 DEFAULT(term->escseq.arg[0], 1);
3026 tmoveto(term->escseq.arg[0]-1, term->c.y);
3027 break;
3028 case 'H': /* CUP -- Move to <row> <col> */
3029 case 'f': /* XXX: HVP -- same? */
3030 DEFAULT(term->escseq.arg[0], 1);
3031 DEFAULT(term->escseq.arg[1], 1);
3032 tmoveto(term->escseq.arg[1]-1, term->escseq.arg[0]-1);
3033 break;
3034 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
3035 case 'J': /* ED -- Clear screen */
3036 term->sel.bx = -1;
3037 switch (term->escseq.arg[0]) {
3038 case 0: /* below */
3039 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
3040 if (term->c.y < term->row-1) tclearregion(0, term->c.y+1, term->col-1, term->row-1);
3041 break;
3042 case 1: /* above */
3043 if (term->c.y > 1) tclearregion(0, 0, term->col-1, term->c.y-1);
3044 tclearregion(0, term->c.y, term->c.x, term->c.y);
3045 break;
3046 case 2: /* all */
3047 tclearregion(0, 0, term->col-1, term->row-1);
3048 break;
3049 default:
3050 goto unknown;
3052 break;
3053 case 'K': /* EL -- Clear line */
3054 switch (term->escseq.arg[0]) {
3055 case 0: /* right */
3056 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
3057 break;
3058 case 1: /* left */
3059 tclearregion(0, term->c.y, term->c.x, term->c.y);
3060 break;
3061 case 2: /* all */
3062 tclearregion(0, term->c.y, term->col-1, term->c.y);
3063 break;
3065 break;
3066 case 'S': /* SU -- Scroll <n> line up */
3067 DEFAULT(term->escseq.arg[0], 1);
3068 tscrollup(term->top, term->escseq.arg[0], 0);
3069 break;
3070 case 'T': /* SD -- Scroll <n> line down */
3071 DEFAULT(term->escseq.arg[0], 1);
3072 tscrolldown(term->top, term->escseq.arg[0]);
3073 break;
3074 case 'L': /* IL -- Insert <n> blank lines */
3075 DEFAULT(term->escseq.arg[0], 1);
3076 tinsertblankline(term->escseq.arg[0]);
3077 break;
3078 case 'l': /* RM -- Reset Mode */
3079 if (term->escseq.priv) {
3080 switch (term->escseq.arg[0]) {
3081 case 1: // 1001 for xterm compatibility
3082 DUMP_KEYPAD_SWITCH("1", "OFF");
3083 term->mode &= ~MODE_APPKEYPAD;
3084 break;
3085 case 5: /* DECSCNM -- Remove reverse video */
3086 if (IS_SET(MODE_REVERSE)) {
3087 term->mode &= ~MODE_REVERSE;
3088 tfulldirt();
3090 break;
3091 case 7: /* autowrap off */
3092 term->mode &= ~MODE_WRAP;
3093 break;
3094 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
3095 break;
3096 case 20: /* non-standard code? */
3097 term->mode &= ~MODE_CRLF;
3098 break;
3099 case 25: /* hide cursor */
3100 if ((term->c.state&CURSOR_HIDE) == 0) {
3101 term->c.state |= CURSOR_HIDE;
3102 term->wantRedraw = 1;
3104 break;
3105 case 1000: /* disable X11 xterm mouse reporting */
3106 term->mode &= ~MODE_MOUSEBTN;
3107 break;
3108 case 1002:
3109 term->mode &= ~MODE_MOUSEMOTION;
3110 break;
3111 case 1004:
3112 term->mode &= ~MODE_FOCUSEVT;
3113 break;
3114 case 1005: /* utf-8 mouse encoding */
3115 case 1006: /* sgr mouse encoding */
3116 case 1015: /* urxvt mouse encoding */
3117 term->mousemode = 1000;
3118 break;
3119 case 1049: /* = 1047 and 1048 */
3120 case 47:
3121 case 1047:
3122 if (IS_SET(MODE_ALTSCREEN)) {
3123 tclearregion(0, 0, term->col-1, term->row-1);
3124 tswapscreen();
3126 if (term->escseq.arg[0] != 1049) break;
3127 case 1048:
3128 tcursor(CURSOR_LOAD);
3129 break;
3130 case 2004: /* reset bracketed paste mode */
3131 term->mode &= ~MODE_BRACPASTE;
3132 break;
3133 default:
3134 goto unknown;
3136 } else {
3137 switch (term->escseq.arg[0]) {
3138 case 3:
3139 term->mode &= ~MODE_DISPCTRL;
3140 break;
3141 case 4:
3142 term->mode &= ~MODE_INSERT;
3143 break;
3144 default:
3145 goto unknown;
3148 break;
3149 case 'M': /* DL -- Delete <n> lines */
3150 DEFAULT(term->escseq.arg[0], 1);
3151 tdeleteline(term->escseq.arg[0]);
3152 break;
3153 case 'X': /* ECH -- Erase <n> char */
3154 DEFAULT(term->escseq.arg[0], 1);
3155 tclearregion(term->c.x, term->c.y, term->c.x + term->escseq.arg[0], term->c.y);
3156 break;
3157 case 'P': /* DCH -- Delete <n> char */
3158 DEFAULT(term->escseq.arg[0], 1);
3159 tdeletechar(term->escseq.arg[0]);
3160 break;
3161 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
3162 case 'd': /* VPA -- Move to <row> */
3163 DEFAULT(term->escseq.arg[0], 1);
3164 tmoveto(term->c.x, term->escseq.arg[0]-1);
3165 break;
3166 case 'h': /* SM -- Set terminal mode */
3167 if (term->escseq.priv) {
3168 switch (term->escseq.arg[0]) {
3169 case 1:
3170 DUMP_KEYPAD_SWITCH("1", "ON");
3171 term->mode |= MODE_APPKEYPAD;
3172 break;
3173 case 5: /* DECSCNM -- Reverve video */
3174 if (!IS_SET(MODE_REVERSE)) {
3175 term->mode |= MODE_REVERSE;
3176 tfulldirt();
3178 break;
3179 case 7:
3180 term->mode |= MODE_WRAP;
3181 break;
3182 case 20:
3183 term->mode |= MODE_CRLF;
3184 break;
3185 case 12: /* att610 -- Start blinking cursor (IGNORED) */
3186 /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
3187 if (term->escseq.narg > 1 && term->escseq.arg[1] != 25) break;
3188 case 25:
3189 if ((term->c.state&CURSOR_HIDE) != 0) {
3190 term->c.state &= ~CURSOR_HIDE;
3191 term->wantRedraw = 1;
3193 break;
3194 case 1000: /* 1000,1002: enable xterm mouse report */
3195 term->mode |= MODE_MOUSEBTN;
3196 break;
3197 case 1002:
3198 term->mode |= MODE_MOUSEMOTION;
3199 break;
3200 case 1004:
3201 term->mode |= MODE_FOCUSEVT;
3202 break;
3203 case 1005: /* utf-8 mouse encoding */
3204 case 1006: /* sgr mouse encoding */
3205 case 1015: /* urxvt mouse encoding */
3206 term->mousemode = term->escseq.arg[0];
3207 break;
3208 case 1049: /* = 1047 and 1048 */
3209 case 47:
3210 case 1047:
3211 if (IS_SET(MODE_ALTSCREEN)) tclearregion(0, 0, term->col-1, term->row-1); else tswapscreen();
3212 if (term->escseq.arg[0] != 1049) break;
3213 case 1048:
3214 tcursor(CURSOR_SAVE);
3215 break;
3216 case 2004: /* set bracketed paste mode */
3217 term->mode |= MODE_BRACPASTE;
3218 break;
3219 default: goto unknown;
3221 } else {
3222 switch (term->escseq.arg[0]) {
3223 case 3:
3224 term->mode |= MODE_DISPCTRL;
3225 break;
3226 case 4:
3227 term->mode |= MODE_INSERT;
3228 break;
3229 default:
3230 goto unknown;
3233 break;
3234 case 'm': /* SGR -- Terminal attribute (color) */
3235 tsetattr(term->escseq.arg, term->escseq.narg);
3236 break;
3237 case 'n':
3238 if (!term->escseq.priv) {
3239 switch (term->escseq.arg[0]) {
3240 case 6: { /* cursor position report */
3241 char buf[32];
3243 sprintf(buf, "\x1b[%d;%dR", term->c.x+1, term->c.y+1);
3244 ttywritestr(buf);
3245 } break;
3248 break;
3249 case 'r': /* DECSTBM -- Set Scrolling Region */
3250 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
3251 // xterm compatibility
3252 DUMP_KEYPAD_SWITCH("1001", "OFF");
3253 term->mode &= ~MODE_APPKEYPAD;
3254 } else if (term->escseq.priv) {
3255 goto unknown;
3256 } else {
3257 DEFAULT(term->escseq.arg[0], 1);
3258 DEFAULT(term->escseq.arg[1], term->row);
3259 tsetscroll(term->escseq.arg[0]-1, term->escseq.arg[1]-1);
3260 tmoveto(0, 0);
3262 break;
3263 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
3264 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
3265 // xterm compatibility
3266 DUMP_KEYPAD_SWITCH("1001", "ON");
3267 term->mode |= MODE_APPKEYPAD;
3268 } else {
3269 tcursor(CURSOR_SAVE);
3271 break;
3272 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
3273 tcursor(CURSOR_LOAD);
3274 break;
3275 default:
3276 unknown:
3277 fprintf(stderr, "erresc: unknown csi ");
3278 csidump();
3279 break;
3284 static void csireset (void) {
3285 memset(&term->escseq, 0, sizeof(term->escseq));
3289 static void tputtab (void) {
3290 int space = opt_tabsize-term->c.x%opt_tabsize;
3292 if (space > 0) tmoveto(term->c.x+space, term->c.y);
3296 ////////////////////////////////////////////////////////////////////////////////
3297 // put char to output buffer or process command
3299 // return 1 if this was control character
3300 // return -1 if this should break esape sequence
3301 static int tputc_ctrl (char ascii) {
3302 int res = 1;
3304 if (term->esc&ESC_TITLE) return 0;
3306 switch (ascii) {
3307 case '\t': tputtab(); break;
3308 case '\b': tmoveto(term->c.x-1, term->c.y); break;
3309 case '\r': tmoveto(0, term->c.y); break;
3310 case '\f': case '\n': case '\v': tnewline(IS_SET(MODE_CRLF)?1:0); break; /* go to first col if the mode is set */
3311 case '\a':
3312 if (!(xw.state & WIN_FOCUSED) && (term->belltype&BELL_URGENT)) xseturgency(1);
3313 if (term->belltype&BELL_AUDIO) XBell(xw.dpy, 100);
3314 break;
3315 case 14: term->charset = MODE_GFX1; break;
3316 case 15: term->charset = MODE_GFX0; break;
3317 case 0x18: case 0x1a: res = -1; break; // do nothing, interrupt current escape sequence
3318 case 127: break; // ignore it
3319 case '\033': csireset(); term->esc = ESC_START; break;
3320 //case 0x9b: csireset(); term->esc = ESC_START | ESC_CSI; break;
3321 default: res = 0; break;
3323 return res;
3327 static void tputc (const char *c) {
3328 char ascii = *c;
3329 int ctl = tputc_ctrl(ascii);
3331 if (ctl > 0) return; // control char; should not break escape sequence
3332 if (ctl < 0) {
3333 // control char; should break escape sequence
3334 term->esc = 0;
3335 return;
3337 //dlogf("tputc: [%c]\n", c[0]);
3338 if (term->esc & ESC_START) {
3339 if (term->esc & ESC_CSI) {
3340 term->escseq.buf[term->escseq.len++] = ascii;
3341 if (BETWEEN(ascii, 0x40, 0x7E) || term->escseq.len >= ESC_BUF_SIZ) {
3342 term->esc = 0;
3343 csiparse();
3344 csihandle();
3346 } else if (term->esc & ESC_OSC) {
3347 /* TODO: handle other OSC */
3348 if (ascii == ';') {
3349 term->title[0] = 0;
3350 term->titlelen = 0;
3351 term->esc = ESC_START | ESC_TITLE;
3352 //updateTabBar = 1;
3354 } else if (term->esc & ESC_TITLE) {
3355 int len = utf8size(c);
3357 if (ascii == '\a' || term->titlelen+len >= ESC_TITLE_SIZ) {
3358 term->esc = 0;
3359 term->title[term->titlelen] = '\0';
3360 fixWindowTitle(term);
3361 updateTabBar = 1;
3362 } else if (len > 0) {
3363 memcpy(term->title+term->titlelen, c, len);
3364 term->titlelen += len;
3365 term->title[term->titlelen] = '\0';
3367 } else if (term->esc & ESC_ALTCHARSET) {
3368 term->esc = 0;
3369 switch (ascii) {
3370 case '0': /* Line drawing crap */
3371 term->mode |= MODE_GFX0;
3372 break;
3373 case 'B': /* Back to regular text */
3374 term->mode &= ~MODE_GFX0;
3375 break;
3376 default:
3377 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
3378 term->mode &= ~MODE_GFX0;
3379 break;
3381 } else if (term->esc & ESC_ALTG1) {
3382 term->esc = 0;
3383 switch (ascii) {
3384 case '0': /* Line drawing crap */
3385 term->mode |= MODE_GFX1;
3386 break;
3387 case 'B': /* Back to regular text */
3388 term->mode &= ~MODE_GFX1;
3389 break;
3390 default:
3391 fprintf(stderr, "esc unhandled charset: ESC ) %c\n", ascii);
3392 term->mode &= ~MODE_GFX1;
3393 break;
3395 } else if (term->esc & ESC_HASH) {
3396 term->esc = 0;
3397 switch (ascii) {
3398 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
3399 //tfillscreenwithE();
3400 break;
3402 } else if (term->esc & ESC_PERCENT) {
3403 term->esc = 0;
3404 } else {
3405 switch (ascii) {
3406 case '[': term->esc |= ESC_CSI; break;
3407 case ']': term->esc |= ESC_OSC; break;
3408 case '(': term->esc |= ESC_ALTCHARSET; break;
3409 case ')': term->esc |= ESC_ALTG1; break;
3410 case '#': term->esc |= ESC_HASH; break;
3411 case '%': term->esc |= ESC_PERCENT; break;
3412 case 'D': /* IND -- Linefeed */
3413 term->esc = 0;
3414 if (term->c.y == term->bot) tscrollup(term->top, 1, 1); else tmoveto(term->c.x, term->c.y+1);
3415 break;
3416 case 'E': /* NEL -- Next line */
3417 term->esc = 0;
3418 tnewline(1); /* always go to first col */
3419 break;
3420 case 'M': /* RI -- Reverse linefeed */
3421 term->esc = 0;
3422 if (term->c.y == term->top) tscrolldown(term->top, 1); else tmoveto(term->c.x, term->c.y-1);
3423 break;
3424 case 'c': /* RIS -- Reset to inital state */
3425 term->esc = 0;
3426 treset();
3427 break;
3428 case '=': /* DECPAM -- Application keypad */
3429 DUMP_KEYPAD_SWITCH("=", "ON");
3430 term->esc = 0;
3431 term->mode |= MODE_APPKEYPAD;
3432 break;
3433 case '>': /* DECPNM -- Normal keypad */
3434 DUMP_KEYPAD_SWITCH(">", "OFF");
3435 term->esc = 0;
3436 term->mode &= ~MODE_APPKEYPAD;
3437 break;
3438 case '7': /* DECSC -- Save Cursor */
3439 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
3440 //TODO?
3441 term->esc = 0;
3442 tcursor(CURSOR_SAVE);
3443 break;
3444 case '8': /* DECRC -- Restore Cursor */
3445 //TODO?
3446 term->esc = 0;
3447 tcursor(CURSOR_LOAD);
3448 break;
3449 case 'Z': /* DEC private identification */
3450 term->esc = 0;
3451 ttywritestr("\x1b[?1;2c");
3452 break;
3453 default:
3454 term->esc = 0;
3455 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X ('%c')\n", (uchar)ascii, isprint(ascii)?ascii:'.');
3456 break;
3459 } else {
3460 //if (term->sel.bx != -1 && BETWEEN(term->c.y, term->sel.by, term->sel.ey)) term->sel.bx = -1;
3461 do {
3462 if (term->needConv && IS_GFX(term->c.attr.attr)) {
3463 ulong cc;
3465 utf8decode(c, &cc);
3466 if (cc < 32 || cc >= 127) break; //FIXME: nothing at all?
3467 } else {
3468 if ((unsigned char)ascii < 32 || ascii == 127) break; // seems that this chars are empty too
3470 if (term->c.state&CURSOR_WRAPNEXT) {
3471 if (IS_SET(MODE_WRAP)) {
3472 // always go to first col
3473 tnewline(2);
3474 } else {
3475 tsetcharwrap(term->c.y, 0);
3476 break; // wrap is off, don't want more chars
3479 tsetchar(c);
3480 if (term->c.x+1 < term->col) tmoveto(term->c.x+1, term->c.y); else term->c.state |= CURSOR_WRAPNEXT;
3481 } while (0);
3486 static void tunshowhistory (void) {
3487 if (term != NULL && term->topline != 0) {
3488 term->topline = 0;
3489 term->wantRedraw = 1;
3490 term->lastDrawTime = 0;
3495 static void tsendfocusevent (int focused) {
3496 if (term != NULL && IS_SET(MODE_FOCUSEVT)) {
3497 ttywritestr("\x1b[");
3498 ttywrite(focused?"I":"O", 1);
3503 static void tcmdlinedirty (void) {
3504 if (term != NULL) {
3505 markDirty(term->row-term->topline-1, 2);
3506 term->wantRedraw = 1;
3511 static void tcmdlinefixofs (void) {
3512 int ofs, len;
3514 len = utf8strlen(term->cmdline);
3515 ofs = len-(term->col-1);
3516 if (ofs < 0) ofs = 0;
3517 for (term->cmdofs = 0; ofs > 0; --ofs) term->cmdofs += utf8size(term->cmdline+term->cmdofs);
3518 tcmdlinedirty();
3522 static void tcmdlinehide (void) {
3523 term->cmdMode = CMDMODE_NONE;
3524 term->cmdprevc = NULL;
3525 tcmdlinedirty();
3529 // utf-8
3530 static void tcmdlinemsg (const char *msg) {
3531 if (msg != NULL) {
3532 int ofs = 0;
3534 term->cmdMode = CMDMODE_MESSAGE;
3535 term->cmdofs = 0;
3536 term->cmdtabpos = -1;
3537 term->cmdprevc = NULL;
3539 while (*msg) {
3540 int len = utf8size(msg);
3542 if (len < 1 || ofs+len >= sizeof(term->cmdline)-1) break;
3543 memcpy(term->cmdline+ofs, msg, len);
3544 ofs += len;
3545 msg += len;
3548 term->cmdline[ofs] = 0;
3549 tcmdlinedirty();
3554 static void tcmdlineinitex (const char *msg) {
3555 term->cmdMode = CMDMODE_INPUT;
3556 term->cmdofs = 0;
3557 term->cmdline[0] = 0;
3558 term->cmdc[0] = 0;
3559 term->cmdcl = 0;
3560 term->cmdtabpos = -1;
3561 term->cmdprevc = NULL;
3562 term->cmdreslen = 0;
3563 term->cmdexecfn = NULL;
3564 if (msg != NULL && msg[0]) {
3565 strcpy(term->cmdline, msg);
3566 term->cmdreslen = strlen(term->cmdline);
3568 tcmdlinefixofs();
3572 static void tcmdlineinit (void) {
3573 tcmdlineinitex(NULL);
3577 static void tcmdlinechoplast (void) {
3578 if (term->cmdcl != 0) {
3579 term->cmdcl = 0;
3580 } else {
3581 if (strlen(term->cmdline) > term->cmdreslen) utf8choplast(term->cmdline);
3583 tcmdlinefixofs();
3587 // utf-8
3588 static void tcmdaddchar (const char *s) {
3589 int len = utf8size(s);
3591 if (len > 0) {
3592 int slen = strlen(term->cmdline);
3594 if (slen+len < sizeof(term->cmdline)) {
3595 memcpy(term->cmdline+slen, s, len);
3596 term->cmdline[slen+len] = 0;
3597 tcmdlinefixofs();
3603 static void tcmdput (const char *s, int len) {
3604 while (len-- > 0) {
3605 int ok;
3607 term->cmdc[term->cmdcl++] = *s++;
3608 term->cmdc[term->cmdcl] = 0;
3610 if ((ok = isfullutf8(term->cmdc, term->cmdcl)) != 0 || term->cmdcl == UTF_SIZ) {
3611 if (ok) tcmdaddchar(term->cmdc);
3612 term->cmdcl = 0;
3618 ////////////////////////////////////////////////////////////////////////////////
3619 // tty resising
3620 static int tresize (int col, int row) {
3621 int mincol = MIN(col, term->col);
3622 int slide = term->c.y-row+1;
3623 Glyph g;
3625 if (col < 1 || row < 1) return 0;
3627 selhide();
3629 g.state = GLYPH_DIRTY;
3630 g.attr = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG;
3631 g.fg = term->deffg;
3632 g.bg = term->defbg;
3633 g.c[0] = ' ';
3634 g.c[1] = 0;
3636 if (slide > 0) {
3637 tsetscroll(0, term->row-1);
3638 for (; slide > 0; --slide) tscrollup(0, 1, 1); // to fill history
3641 if (row < term->row) {
3642 /* free unneeded rows */
3643 for (int f = row; f < term->row; ++f) free(term->alt[f]);
3644 for (int f = term->linecount-(term->row-row); f < term->linecount; ++f) free(term->line[f]);
3645 term->linecount -= (term->row-row);
3646 /* resize to new height */
3647 term->alt = realloc(term->alt, row*sizeof(Line));
3648 term->line = realloc(term->line, term->linecount*sizeof(Line));
3649 } else if (row > term->row) {
3650 /* resize to new height */
3651 term->alt = realloc(term->alt, row*sizeof(Line));
3652 term->line = realloc(term->line, (row+term->maxhistory)*sizeof(Line));
3653 /* add more lines */
3654 for (int f = term->row; f < row; ++f) {
3655 term->alt[f] = calloc(col, sizeof(Glyph));
3656 for (int x = 0; x < col; ++x) term->alt[f][x] = g;
3658 for (int f = 0; f < row-term->row; ++f) {
3659 int y = term->linecount++;
3661 term->line[y] = calloc(col, sizeof(Glyph));
3662 for (int x = 0; x < col; ++x) term->line[y][x] = g;
3666 if (row != term->row) {
3667 term->dirty = realloc(term->dirty, row*sizeof(*term->dirty));
3670 /* resize each row to new width, zero-pad if needed */
3671 for (int f = 0; f < term->linecount; ++f) {
3672 term->line[f] = realloc(term->line[f], col*sizeof(Glyph));
3673 for (int x = mincol; x < col; ++x) term->line[f][x] = g;
3674 if (f < row) {
3675 markDirty(f, 2);
3676 term->alt[f] = realloc(term->alt[f], col*sizeof(Glyph));
3677 for (int x = mincol; x < col; ++x) term->alt[f][x] = g;
3680 /* update terminal size */
3681 term->topline = 0;
3682 term->col = col;
3683 term->row = row;
3684 /* make use of the LIMIT in tmoveto */
3685 tmoveto(term->c.x, term->c.y);
3686 /* reset scrolling region */
3687 tsetscroll(0, row-1);
3688 tfulldirt();
3689 return (slide > 0);
3693 static void xresize (int col, int row) {
3694 Pixmap newbuf;
3695 int oldw, oldh;
3697 if (term == NULL) return;
3698 oldw = term->picbufw;
3699 oldh = term->picbufh;
3700 term->picbufw = MAX(1, col*xw.cw);
3701 term->picbufh = MAX(1, row*xw.ch);
3702 newbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3703 XCopyArea(xw.dpy, term->picbuf, newbuf, dc.gc, 0, 0, term->picbufw, term->picbufh, 0, 0);
3704 XFreePixmap(xw.dpy, term->picbuf);
3705 XSetForeground(xw.dpy, dc.gc, getColor(term->defbg));
3706 if (term->picbufw > oldw) {
3707 XFillRectangle(xw.dpy, newbuf, dc.gc, oldw, 0, term->picbufw-oldw, MIN(term->picbufh, oldh));
3708 } else if (term->picbufw < oldw && xw.w > term->picbufw) {
3709 XClearArea(xw.dpy, xw.win, term->picbufw, 0, xw.w-term->picbufh, MIN(term->picbufh, oldh), False);
3711 if (term->picbufh > oldh) {
3712 XFillRectangle(xw.dpy, newbuf, dc.gc, 0, oldh, term->picbufw, term->picbufh-oldh);
3713 } else if (term->picbufh < oldh && xw.h > term->picbufh) {
3714 XClearArea(xw.dpy, xw.win, 0, term->picbufh, xw.w, xw.h-term->picbufh, False);
3716 term->picbuf = newbuf;
3717 tfulldirt();
3718 updateTabBar = 1;
3722 ////////////////////////////////////////////////////////////////////////////////
3723 // x11 drawing and utils
3725 static void xcreatebw (void) {
3726 if ((dc.bcol = calloc(MAX_COLOR+1, sizeof(dc.bcol[0]))) == NULL) die("out of memory");
3728 for (int f = 0; f <= MAX_COLOR; ++f) {
3729 XColor nclr;
3731 nclr = dc.ncol[f].pixel;
3732 XQueryColor(xw.dpy, xw.cmap, &nclr);
3733 fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", f, nclr.red, nclr.green, nclr.blue);
3739 static void xallocbwclr (int idx, XColor *color) {
3740 double lumi;
3742 XQueryColor(xw.dpy, xw.cmap, color);
3743 //fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", idx, color->red, color->green, color->blue);
3745 lumi = 0.3*((double)color->red/65535.0)+0.59*((double)color->green/65535.0)+0.11*((double)color->blue/65535.0);
3746 color->red = color->green = color->blue = (int)(lumi*65535.0);
3747 if (!XAllocColor(xw.dpy, xw.cmap, color)) {
3748 fprintf(stderr, "WARNING: could not allocate b/w color #%d\n", idx);
3749 return;
3751 dc.bcol[idx] = color->pixel;
3752 color->red = color->blue = 0;
3753 if (!XAllocColor(xw.dpy, xw.cmap, color)) {
3754 fprintf(stderr, "WARNING: could not allocate b/w color #%d\n", idx);
3755 return;
3757 dc.gcol[idx] = color->pixel;
3761 static void xallocnamedclr (int idx, const char *cname) {
3762 XColor color;
3764 if (!XAllocNamedColor(xw.dpy, xw.cmap, cname, &color, &color)) {
3765 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", idx, cname);
3766 return;
3768 dc.ncol[idx] = color.pixel;
3769 xallocbwclr(idx, &color);
3773 static void xloadcols (void) {
3774 int f, r, g, b;
3775 XColor color;
3776 ulong white = WhitePixel(xw.dpy, xw.scr);
3778 if ((dc.clrs[0] = dc.ncol = calloc(MAX_COLOR+1, sizeof(dc.ncol[0]))) == NULL) die("out of memory");
3779 if ((dc.clrs[1] = dc.bcol = calloc(MAX_COLOR+1, sizeof(dc.bcol[0]))) == NULL) die("out of memory");
3780 if ((dc.clrs[2] = dc.gcol = calloc(MAX_COLOR+1, sizeof(dc.gcol[0]))) == NULL) die("out of memory");
3782 for (f = 0; f <= MAX_COLOR; ++f) dc.ncol[f] = dc.bcol[f] = white;
3783 /* load colors [0-15] */
3784 for (f = 0; f <= 15; ++f) {
3785 const char *cname = opt_colornames[f]!=NULL?opt_colornames[f]:defcolornames[f];
3787 xallocnamedclr(f, cname);
3789 /* load colors [256-...] */
3790 for (f = 256; f <= MAX_COLOR; ++f) {
3791 const char *cname = opt_colornames[f];
3793 if (cname == NULL) {
3794 if (LEN(defextcolornames) <= f-256) continue;
3795 cname = defextcolornames[f-256];
3797 if (cname == NULL) continue;
3798 xallocnamedclr(f, cname);
3800 /* load colors [16-255] ; same colors as xterm */
3801 for (f = 16, r = 0; r < 6; ++r) {
3802 for (g = 0; g < 6; ++g) {
3803 for (b = 0; b < 6; ++b) {
3804 if (opt_colornames[f] != NULL) {
3805 xallocnamedclr(f, opt_colornames[f]);
3806 } else {
3807 color.red = r == 0 ? 0 : 0x3737+0x2828*r;
3808 color.green = g == 0 ? 0 : 0x3737+0x2828*g;
3809 color.blue = b == 0 ? 0 : 0x3737+0x2828*b;
3810 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3811 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3812 } else {
3813 dc.ncol[f] = color.pixel;
3814 xallocbwclr(f, &color);
3817 ++f;
3821 for (r = 0; r < 24; ++r, ++f) {
3822 if (opt_colornames[f] != NULL) {
3823 xallocnamedclr(f, opt_colornames[f]);
3824 } else {
3825 color.red = color.green = color.blue = 0x0808+0x0a0a*r;
3826 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3827 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3828 } else {
3829 dc.ncol[f] = color.pixel;
3830 xallocbwclr(f, &color);
3835 for (int f = 0; f < LEN(opt_colornames); ++f) if (opt_colornames[f]) free(opt_colornames[f]);
3839 static void xclear (int x1, int y1, int x2, int y2) {
3840 XSetForeground(xw.dpy, dc.gc, getColor(IS_SET(MODE_REVERSE) ? term->deffg : term->defbg));
3841 XFillRectangle(xw.dpy, term->picbuf, dc.gc, x1*xw.cw, y1*xw.ch, (x2-x1+1)*xw.cw, (y2-y1+1)*xw.ch);
3845 static void xhints (void) {
3846 XClassHint class = {opt_class, opt_title};
3847 XWMHints wm = {.flags = InputHint, .input = 1};
3848 XSizeHints size = {
3849 .flags = PSize | PResizeInc | PBaseSize,
3850 .height = xw.h,
3851 .width = xw.w,
3852 .height_inc = xw.ch,
3853 .width_inc = xw.cw,
3854 .base_height = xw.h/*xw.tabheight*/,
3855 .base_width = xw.w,
3857 //XSetWMNormalHints(xw.dpy, xw.win, &size);
3858 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, &size, &wm, &class);
3859 XSetWMProtocols(xw.dpy, xw.win, &XA_WM_DELETE_WINDOW, 1);
3863 static XFontSet xinitfont (const char *fontstr) {
3864 XFontSet set;
3865 char *def, **missing;
3866 int n;
3868 missing = NULL;
3869 set = XCreateFontSet(xw.dpy, fontstr, &missing, &n, &def);
3870 if (missing) {
3871 while (n--) fprintf(stderr, "sterm: missing fontset: %s\n", missing[n]);
3872 XFreeStringList(missing);
3874 return set;
3878 static void xgetfontinfo (XFontSet set, int *ascent, int *descent, short *lbearing, short *rbearing, Font *fid) {
3879 XFontStruct **xfonts;
3880 char **font_names;
3881 int n;
3883 *ascent = *descent = *lbearing = *rbearing = 0;
3884 n = XFontsOfFontSet(set, &xfonts, &font_names);
3885 for (int f = 0; f < n; ++f) {
3886 if (f == 0) *fid = (*xfonts)->fid;
3887 *ascent = MAX(*ascent, (*xfonts)->ascent);
3888 *descent = MAX(*descent, (*xfonts)->descent);
3889 *lbearing = MAX(*lbearing, (*xfonts)->min_bounds.lbearing);
3890 *rbearing = MAX(*rbearing, (*xfonts)->max_bounds.rbearing);
3891 ++xfonts;
3896 static void initfonts (const char *fontstr, const char *bfontstr, const char *tabfont) {
3897 if ((dc.font[0].set = xinitfont(fontstr)) == NULL) {
3898 if ((dc.font[0].set = xinitfont(FONT)) == NULL) die("can't load font %s", fontstr);
3900 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);
3902 if ((dc.font[1].set = xinitfont(bfontstr)) == NULL) {
3903 if ((dc.font[1].set = xinitfont(FONTBOLD)) == NULL) die("can't load font %s", bfontstr);
3905 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);
3907 if ((dc.font[2].set = xinitfont(tabfont)) == NULL) {
3908 if ((dc.font[2].set = xinitfont(FONTTAB)) == NULL) die("can't load font %s", tabfont);
3910 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);
3914 static void xinit (void) {
3915 XSetWindowAttributes attrs;
3916 Window parent;
3917 XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
3919 if (!(xw.dpy = XOpenDisplay(NULL))) die("can't open display");
3921 XA_VT_SELECTION = XInternAtom(xw.dpy, "_STERM_SELECTION_", 0);
3922 XA_CLIPBOARD = XInternAtom(xw.dpy, "CLIPBOARD", 0);
3923 XA_UTF8 = XInternAtom(xw.dpy, "UTF8_STRING", 0);
3924 XA_NETWM_NAME = XInternAtom(xw.dpy, "_NET_WM_NAME", 0);
3925 XA_TARGETS = XInternAtom(xw.dpy, "TARGETS", 0);
3926 XA_WM_DELETE_WINDOW = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", 0);
3927 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
3929 xw.scr = XDefaultScreen(xw.dpy);
3930 /* font */
3931 initfonts(opt_fontnorm, opt_fontbold, opt_fonttab);
3932 /* XXX: Assuming same size for bold font */
3933 xw.cw = dc.font[0].rbearing-dc.font[0].lbearing;
3934 xw.ch = dc.font[0].ascent+dc.font[0].descent;
3935 xw.tch = dc.font[2].ascent+dc.font[2].descent;
3936 xw.tabheight = opt_disabletabs ? 0 : xw.tch+2;
3937 //fprintf(stderr, "tabh: %d\n", xw.tabheight);
3938 //xw.tabheight = 0;
3939 /* colors */
3940 xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
3941 xloadcols();
3942 /* window - default size */
3943 term->picbufh = term->row*xw.ch;
3944 term->picbufw = term->col*xw.cw;
3946 xw.h = term->picbufh+xw.tabheight;
3947 xw.w = term->picbufw;
3949 attrs.background_pixel = getColor(defaultBG);
3950 attrs.border_pixel = getColor(defaultBG);
3951 attrs.bit_gravity = NorthWestGravity;
3952 attrs.event_mask = FocusChangeMask | KeyPressMask
3953 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
3954 | /*ButtonMotionMask*/ PointerMotionMask | ButtonPressMask | ButtonReleaseMask
3955 | EnterWindowMask | LeaveWindowMask;
3956 attrs.colormap = xw.cmap;
3957 //fprintf(stderr, "oe: [%s]\n", opt_embed);
3958 parent = opt_embed ? strtol(opt_embed, NULL, 0) : XRootWindow(xw.dpy, xw.scr);
3959 xw.win = XCreateWindow(xw.dpy, parent, 0, 0,
3960 xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
3961 XDefaultVisual(xw.dpy, xw.scr),
3962 CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask
3963 | CWColormap,
3964 &attrs);
3965 xhints();
3966 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3967 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
3968 /* input methods */
3969 if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) die("XOpenIM() failed");
3970 xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL);
3971 /* gc */
3972 dc.gc = XCreateGC(xw.dpy, xw.win, 0, NULL);
3973 /* white cursor, black outline */
3974 xw.cursor = XCreateFontCursor(xw.dpy, XC_xterm);
3975 XDefineCursor(xw.dpy, xw.win, xw.cursor);
3976 XRecolorCursor(xw.dpy, xw.cursor,
3977 &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff},
3978 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
3979 fixWindowTitle(term);
3980 //XStoreName(xw.dpy, xw.win, opt_title);
3982 XSetForeground(xw.dpy, dc.gc, 0);
3983 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
3984 if (xw.tabheight > 0) XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
3986 XMapWindow(xw.dpy, xw.win);
3988 #if BLANKPTR_USE_GLYPH_CURSOR
3989 xw.blankPtr = XCreateGlyphCursor(xw.dpy, dc.font[0].fid, dc.font[0].fid, ' ', ' ', &blackcolor, &blackcolor);
3990 #else
3991 static const char cmbmp[1] = {0};
3992 Pixmap pm;
3994 pm = XCreateBitmapFromData(xw.dpy, xw.win, cmbmp, 1, 1);
3995 xw.blankPtr = XCreatePixmapCursor(xw.dpy, pm, pm, &blackcolor, &blackcolor, 0, 0);
3996 XFreePixmap(xw.dpy, pm);
3997 #endif
3999 XSync(xw.dpy, 0);
4003 static void xblankPointer (void) {
4004 if (!ptrBlanked && xw.blankPtr != None) {
4005 ptrBlanked = 1;
4006 XDefineCursor(xw.dpy, xw.win, xw.blankPtr);
4007 XFlush(xw.dpy);
4012 static void xunblankPointer (void) {
4013 if (ptrBlanked && xw.cursor != None) {
4014 ptrBlanked = 0;
4015 XDefineCursor(xw.dpy, xw.win, xw.cursor);
4016 XFlush(xw.dpy);
4017 ptrLastMove = mclock_ticks();
4022 static void xdraws (const char *s, const Glyph *base, int x, int y, int charlen, int bytelen) {
4023 int fg = base->fg, bg = base->bg, temp;
4024 int winx = x*xw.cw, winy = y*xw.ch+dc.font[0].ascent, width = charlen*xw.cw;
4025 XFontSet fontset = dc.font[0].set;
4026 int defF = base->attr&ATTR_DEFFG, defB = base->attr&ATTR_DEFBG;
4028 /* only switch default fg/bg if term is in RV mode */
4029 if (IS_SET(MODE_REVERSE)) {
4030 if (defF) fg = term->defbg;
4031 if (defB) bg = term->deffg;
4033 if (base->attr&ATTR_REVERSE) defF = defB = 0;
4034 if (base->attr&ATTR_BOLD) {
4035 if (defF && defB && defaultBoldFG >= 0) fg = defaultBoldFG;
4036 else if (fg < 8) fg += 8;
4037 fontset = dc.font[1].set;
4039 if ((base->attr&ATTR_UNDERLINE) && defaultUnderlineFG >= 0) {
4040 if (defF && defB) fg = defaultUnderlineFG;
4043 if (base->attr&ATTR_REVERSE) { temp = fg; fg = bg; bg = temp; }
4045 XSetBackground(xw.dpy, dc.gc, getColor(bg));
4046 XSetForeground(xw.dpy, dc.gc, getColor(fg));
4050 FILE *fo = fopen("zlog.log", "ab");
4051 fprintf(fo, "xdraws: x=%d; y=%d; charlen=%d; bytelen=%d\n", x, y, charlen, bytelen);
4052 fclose(fo);
4056 if (IS_GFX(base->attr)) {
4057 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
4058 } else if (!needConversion) {
4059 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
4060 } else {
4061 if (bytelen > 0) {
4062 //k8: dunno why, but Xutf8DrawImageString() ignores ascii (at least for terminus)
4063 const char *pos = s;
4064 int xpos = winx;
4066 while (pos < s+bytelen) {
4067 const char *e;
4068 int clen;
4070 if ((unsigned char)(pos[0]) < 128) {
4071 for (e = pos+1; e < s+bytelen && (unsigned char)(*e) < 128; ++e) ;
4072 clen = e-pos;
4073 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
4076 FILE *fo = fopen("zlog.log", "ab");
4077 fprintf(fo, " norm: clen=%d (%d); [", clen, (int)(e-pos));
4078 fwrite(pos, 1, e-pos, fo);
4079 fprintf(fo, "]\n");
4080 fclose(fo);
4083 } else {
4084 for (clen = 0, e = pos; e < s+bytelen && (unsigned char)(*e) >= 128; ++e) {
4085 if (((unsigned char)(e[0])&0xc0) == 0xc0) ++clen;
4087 Xutf8DrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
4090 FILE *fo = fopen("zlog.log", "ab");
4091 fprintf(fo, " utf8: clen=%d (%d); [", clen, (int)(e-pos));
4092 fwrite(pos, 1, e-pos, fo);
4093 fprintf(fo, "]\n");
4094 fclose(fo);
4098 xpos += xw.cw*clen;
4099 pos = e;
4104 if (opt_drawunderline && (base->attr&ATTR_UNDERLINE)) {
4105 XDrawLine(xw.dpy, term->picbuf, dc.gc, winx, winy+1, winx+width-1, winy+1);
4110 /* copy buffer pixmap to screen pixmap */
4111 static void xcopy (int x, int y, int cols, int rows) {
4112 int src_x = x*xw.cw, src_y = y*xw.ch, src_w = cols*xw.cw, src_h = rows*xw.ch;
4113 int dst_x = src_x, dst_y = src_y;
4115 if (opt_tabposition == 1) { dst_y += xw.tabheight; }
4116 XCopyArea(xw.dpy, term->picbuf, xw.win, dc.gc, src_x, src_y, src_w, src_h, dst_x, dst_y);
4120 static void xdrawcursor (void) {
4121 Glyph g;
4122 int sl, scrx, scry, cmy;
4124 if (term == NULL) return;
4126 LIMIT(term->oldcx, 0, term->col-1);
4127 LIMIT(term->oldcy, 0, term->row-1);
4129 cmy = term->row-term->topline-1;
4131 if (!(xw.state&WIN_FOCUSED) && !term->curblinkinactive) term->curbhidden = 0;
4133 if (term->cmdMode == CMDMODE_NONE || term->oldcy != cmy) {
4134 scrx = term->oldcx;
4135 scry = term->oldcy+term->topline;
4136 if (scry >= 0 && scry < term->row) {
4137 if (term->curbhidden < 0 ||
4138 term->oldcy != term->c.y || term->oldcx != term->c.x ||
4139 (term->c.state&CURSOR_HIDE) ||
4140 !(xw.state&WIN_FOCUSED)) {
4141 /* remove the old cursor */
4142 sl = utf8size(term->line[term->oldcy][scrx].c);
4143 g = term->line[term->oldcy][scrx];
4144 if (selected(scrx, term->c.y)) g.attr ^= ATTR_REVERSE;
4145 xdraws(g.c, &g, scrx, scry, 1, sl);
4146 //xclear(scrx, term->oldcy, scrx, term->oldcy);
4147 xcopy(scrx, scry, 1, 1);
4149 if (term->curbhidden) term->curbhidden = 1;
4153 if (term->cmdMode != CMDMODE_NONE && term->oldcy == cmy) return;
4154 if ((term->c.state&CURSOR_HIDE) != 0) return;
4155 if (term->curbhidden) return;
4156 /* draw the new one */
4157 scrx = term->c.x;
4158 scry = term->c.y+term->topline;
4159 if (scry >= 0 && scry < term->row) {
4160 int nodraw = 0;
4162 if (!(xw.state&WIN_FOCUSED)) {
4163 if (defaultCursorInactiveBG < 0) {
4164 XSetForeground(xw.dpy, dc.gc, getColor(defaultCursorBG));
4165 XDrawRectangle(xw.dpy, term->picbuf, dc.gc, scrx*xw.cw, scry*xw.ch, xw.cw-1, xw.ch-1);
4166 nodraw = 1;
4167 } else {
4168 g.bg = defaultCursorInactiveBG;
4169 g.fg = defaultCursorInactiveFG;
4171 } else {
4172 g.fg = defaultCursorFG;
4173 g.bg = defaultCursorBG;
4175 if (!nodraw) {
4176 memcpy(g.c, term->line[term->c.y][scrx].c, UTF_SIZ);
4177 g.state = 0;
4178 g.attr = ATTR_NULL;
4179 if (IS_SET(MODE_REVERSE)) g.attr |= ATTR_REVERSE;
4180 sl = utf8size(g.c);
4181 xdraws(g.c, &g, scrx, scry, 1, sl);
4183 term->oldcx = scrx;
4184 term->oldcy = term->c.y;
4185 xcopy(scrx, scry, 1, 1);
4190 static void xdrawTabBar (void) {
4191 if (xw.tabheight > 0 && updateTabBar) {
4192 if (updateTabBar > 0) {
4193 int tabw = xw.w/opt_tabcount;
4194 XFontSet fontset = dc.font[2].set;
4196 XSetForeground(xw.dpy, dc.gc, getColor(normalTabBG));
4197 XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
4199 for (int f = firstVisibleTab; f < firstVisibleTab+opt_tabcount; ++f) {
4200 int x = (f-firstVisibleTab)*tabw;
4201 const char *title;
4202 char *tit;
4204 if (f >= term_count) {
4205 XSetForeground(xw.dpy, dc.gc, getColor(normalTabBG));
4206 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, xw.w, xw.tabheight);
4208 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
4209 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
4210 break;
4212 title = term_array[f]->title;
4213 if (!title[0]) title = opt_title;
4214 tit = SPrintf("[%d]%s", f, title);
4215 title = tit;
4217 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
4218 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, tabw, xw.tabheight);
4220 XSetBackground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
4221 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabFG : normalTabFG));
4223 if (needConversion) {
4224 int xx = x+2;
4226 while (*title && xx < x+tabw) {
4227 const char *e = title;
4228 XRectangle r;
4230 memset(&r, 0, sizeof(r));
4232 if ((unsigned char)(*e) > 127) {
4233 while (*e && (unsigned char)(*e) > 127) ++e;
4234 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
4235 Xutf8TextExtents(fontset, title, e-title, &r, NULL);
4236 } else {
4237 while (*e && (unsigned char)(*e) <= 127) ++e;
4238 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
4239 XmbTextExtents(fontset, title, e-title, &r, NULL);
4241 title = e;
4242 xx += r.width-r.x;
4245 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
4246 } else {
4247 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
4249 free(tit);
4251 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
4252 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x+tabw-2, 0, 2, xw.tabheight);
4254 if (f > firstVisibleTab) {
4255 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
4256 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
4260 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
4261 //XDrawRectangle(xw.dpy, xw.pictab, dc.gc, -1, 0, xw.w+1, xw.tabheight+1);
4262 if (opt_tabposition == 0) {
4263 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, 0);
4264 } else {
4265 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, xw.tabheight-1, xw.w, xw.tabheight-1);
4269 if (opt_tabposition == 0) {
4270 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, xw.h-xw.tabheight);
4271 } else {
4272 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, 0);
4275 updateTabBar = 0;
4279 static void drawcmdline (int scry) {
4280 Glyph base;
4281 int cpos = term->cmdofs, bc = 0, x, sx;
4282 int back = (term->cmdMode == CMDMODE_INPUT ? 21 : 124);
4283 int rslen = (term->cmdMode == CMDMODE_INPUT ? term->cmdreslen : 32760);
4285 base.attr = ATTR_NULL;
4286 base.fg = 255;
4287 base.bg = back;
4288 base.state = 0;
4289 // hilighted
4290 for (sx = x = 0; x < term->col && term->cmdline[cpos] && cpos < rslen; ++x) {
4291 int l = utf8size(term->cmdline+cpos);
4293 if (bc+l > DRAW_BUF_SIZ) {
4294 xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
4295 bc = 0;
4296 sx = x;
4298 memcpy(term->drawbuf+bc, term->cmdline+cpos, l);
4299 cpos += l;
4300 bc += l;
4302 if (bc > 0) xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
4303 // user input
4304 bc = 0;
4305 base.fg = 220;
4306 for (sx = x; x < term->col && term->cmdline[cpos]; ++x) {
4307 int l = utf8size(term->cmdline+cpos);
4309 if (bc+l > DRAW_BUF_SIZ) {
4310 xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
4311 bc = 0;
4312 sx = x;
4314 memcpy(term->drawbuf+bc, term->cmdline+cpos, l);
4315 cpos += l;
4316 bc += l;
4318 if (bc > 0) xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
4320 if (x < term->col && term->cmdMode == CMDMODE_INPUT) {
4321 base.bg = base.fg;
4322 base.fg = back;
4323 xdraws(" ", &base, x, scry, 1, 1);
4324 ++x;
4327 if (x < term->col) {
4328 base.fg = 255;
4329 base.bg = back;
4330 memset(term->drawbuf, ' ', DRAW_BUF_SIZ);
4331 while (x < term->col) {
4332 sx = x;
4333 x += DRAW_BUF_SIZ;
4334 if (x > term->col) x = term->col;
4335 xdraws(term->drawbuf, &base, sx, scry, x-sx, x-sx);
4339 xcopy(0, scry, term->col, 1);
4343 static void drawline (int x1, int x2, int scry, int lineno, int dontcopy) {
4344 //fprintf(stderr, "%d: drawline: x1=%d; x2=%d; scry=%d; row:%d; lineno=%d\n", mclock_ticks(), x1, x2, scry, term->row, lineno);
4345 if (scry < 0 || scry >= term->row) return;
4346 if (scry == term->row-1 && term->cmdMode != CMDMODE_NONE) { drawcmdline(scry); return; }
4347 if (lineno < 0 || lineno >= term->linecount) {
4348 xclear(0, scry, term->col-1, scry);
4349 xcopy(0, scry, term->col, 1);
4350 } else {
4351 int ic, ib, ox, sl;
4352 int stx, ex;
4353 Glyph base, new;
4354 Line l = term->line[lineno];
4356 if (lineno < term->row && term->topline == 0) {
4357 if (!term->dirty[lineno]) return;
4358 if (!dontcopy) {
4359 // fix 'dirty' flag for line
4360 if (term->dirty[lineno]&0x02) {
4361 // mark full line as dirty
4362 stx = 0;
4363 ex = term->col;
4364 term->dirty[lineno] = 0;
4365 } else {
4366 term->dirty[lineno] = 0;
4367 if (x1 > 0) for (int x = 0; x < x1; ++x) if (l[x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
4368 if (!term->dirty[lineno] && x2 < term->col) for (int x = x2; x < term->col; ++x) if (l[x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
4370 // find dirty region
4371 for (stx = x1; stx < x2; ++stx) if (l[stx].state&GLYPH_DIRTY) break;
4372 for (ex = x2; ex > stx; --ex) if (l[ex-1].state&GLYPH_DIRTY) break;
4373 if (stx >= x2 || ex <= stx) return; // nothing to do
4375 } else {
4376 // 'dontcopy' means that the whole screen is dirty
4377 stx = 0;
4378 ex = term->col;
4379 term->dirty[lineno] = 0;
4381 } else {
4382 //if (lineno < term->row) term->dirty[lineno] = 0;
4383 stx = 0;
4384 ex = term->col;
4387 base = l[stx];
4388 if (term->sel.bx != -1 && selected(stx, lineno)) base.attr ^= ATTR_REVERSE;
4389 ic = ib = 0;
4390 ox = stx;
4391 //xclear(stx, scry, ex-1, scry); //k8: actually, we don't need this for good monospace fonts, so...
4392 for (int x = stx; x < ex; ++x) {
4393 new = l[x];
4394 l[x].state &= ~GLYPH_DIRTY; //!
4395 if (term->sel.bx != -1 && selected(x, lineno)) new.attr ^= ATTR_REVERSE;
4396 if (ib > 0 && (ATTRCMP(base, new) || ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
4397 // flush draw buffer
4398 xdraws(term->drawbuf, &base, ox, scry, ic, ib);
4399 ic = ib = 0;
4401 if (ib == 0) { ox = x; base = new; }
4402 sl = utf8size(new.c);
4403 memcpy(term->drawbuf+ib, new.c, sl);
4404 ib += sl;
4405 ++ic;
4407 if (ib > 0) xdraws(term->drawbuf, &base, ox, scry, ic, ib);
4408 //xcopy(0, scry, term->col, 1);
4409 //if (term->c.y == lineno && term->c.x >= stx && term->c.x < ex) xdrawcursor();
4410 if (!dontcopy) xcopy(stx, scry, ex-stx, 1);
4415 static void drawregion (int x1, int y1, int x2, int y2, int forced) {
4416 int fulldirty = 0;
4418 if (!forced && (xw.state&WIN_VISIBLE) == 0) {
4419 //dlogf("invisible");
4420 lastDrawTime = term->lastDrawTime = 1;
4421 term->wantRedraw = 1;
4422 return;
4425 if (y1 == 0 && y2 == term->row) {
4426 fulldirty = 1;
4427 for (int y = 0; y < y2; ++y) if (!(term->dirty[y]&0x02)) { fulldirty = 0; break; }
4430 if (term->topline < term->row) {
4431 for (int y = y1; y < y2; ++y) drawline(x1, x2, y+term->topline, y, fulldirty);
4433 if (term->topline > 0) {
4434 int scry = MIN(term->topline, term->row), y = term->row;
4436 fulldirty = 1;
4437 if (term->topline >= term->row) y += term->topline-term->row;
4438 while (--scry >= 0) {
4439 drawline(0, term->col, scry, y, 0);
4440 ++y;
4443 if (fulldirty) xcopy(0, 0, term->col, term->row);
4444 xdrawcursor();
4445 xdrawTabBar();
4446 //XFlush(xw.dpy);
4447 lastDrawTime = term->lastDrawTime = mclock_ticks();
4448 term->wantRedraw = 0;
4452 static void draw (int forced) {
4453 if (term != NULL) {
4454 //fprintf(stderr, "draw(%d) (%d)\n", forced, mclock_ticks());
4455 drawregion(0, 0, term->col, term->row, forced);
4461 static int xisyinunused (int y) {
4462 if (xw.tabheight > 0 && term != NULL) {
4463 int unh = xw.h-(term->row*xw.ch)-xw.tabheight;
4465 if (unh > 0) {
4466 switch (opt_tabposition) {
4467 case 0: // bottom
4468 return (y >= xw.h-xw.tabheight-unh && y < xw.h-xw.tabheight);
4469 break;
4470 case 1: // top
4471 return (y >= xw.tabheight+(term->row*xw.ch)t);
4472 break;
4476 return 0;
4481 static void xclearunused (void) {
4482 if (xw.tabheight > 0 && term != NULL) {
4483 int unh = xw.h-(term->row*xw.ch)-xw.tabheight;
4485 if (unh > 0) {
4486 switch (opt_tabposition) {
4487 case 0: // bottom
4488 XClearArea(xw.dpy, xw.win, 0, xw.h-xw.tabheight-unh, xw.w, unh, False);
4489 break;
4490 case 1: // top
4491 XClearArea(xw.dpy, xw.win, 0, xw.tabheight+(term->row*xw.ch), xw.w, unh, False);
4492 break;
4499 static void expose (XEvent *ev) {
4500 XExposeEvent *e = &ev->xexpose;
4502 if (xw.state&WIN_REDRAW) {
4503 if (!e->count && term != NULL) {
4504 xw.state &= ~WIN_REDRAW;
4505 xcopy(0, 0, term->col, term->row);
4506 updateTabBar = -1;
4508 xclearunused();
4509 } else if (term != NULL) {
4510 int taby = (opt_tabposition==1 ? 0 : xw.h-xw.tabheight);
4511 int termy = (opt_tabposition==1 ? xw.tabheight : 0);
4512 int x0 = e->x/xw.cw, y0 = (e->y-termy)/xw.ch;
4513 int x1 = (e->x+e->width)/xw.cw, y1 = (e->y-termy+e->height)/xw.ch;
4515 //fprintf(stderr, "x=%d; y=%d; w=%d; h=%d\n", e->x, e->y, e->width, e->height);
4516 //fprintf(stderr, "taby=%d; tabh=%d\n", taby, xw.tabheight);
4517 //fprintf(stderr, "x0=%d; y0=%d; x1=%d; y1=%d\n", x0, y0, x1, y1);
4519 if (e->y <= taby+xw.tabheight && e->y+e->height >= taby) {
4520 //fprintf(stderr, "tabbar!\n");
4521 updateTabBar = -1;
4523 //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)));
4524 //xcopy(0, 0, term->col, term->row);
4525 //xclear(x0, y0, x1-x0+1, y1-y0+1);
4526 LIMIT(x0, 0, term->col-1);
4527 LIMIT(x1, 0, term->col-1);
4528 LIMIT(y0, 0, term->row-1);
4529 LIMIT(y1, 0, term->row-1);
4530 //fprintf(stderr, "*:x0=%d; y0=%d; x1=%d; y1=%d\n", x0, y0, x1, y1);
4531 xcopy(x0, y0, x1-x0+1, y1-y0+1);
4532 //xclearunused(); //FIXME: optimize this
4533 } else {
4534 updateTabBar = -1;
4536 xdrawTabBar();
4537 //XFlush(xw.dpy);
4541 static void visibility (XEvent *ev) {
4542 XVisibilityEvent *e = &ev->xvisibility;
4544 if (e->state == VisibilityFullyObscured) xw.state &= ~WIN_VISIBLE;
4545 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 */
4549 static void unmap (XEvent *ev) {
4550 xw.state &= ~WIN_VISIBLE;
4554 static void xseturgency (int add) {
4555 XWMHints *h = XGetWMHints(xw.dpy, xw.win);
4557 h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
4558 XSetWMHints(xw.dpy, xw.win, h);
4559 XFree(h);
4563 static void focus (XEvent *ev) {
4564 if (ev->type == FocusIn) {
4565 xw.state |= WIN_FOCUSED;
4566 xseturgency(0);
4567 tsendfocusevent(1);
4568 } else {
4569 xw.state &= ~WIN_FOCUSED;
4570 tsendfocusevent(0);
4572 //draw(1);
4573 updateTabBar = -1;
4574 xdrawTabBar();
4575 xdrawcursor();
4576 //xcopy(0, 0, term->col, term->row);
4580 ////////////////////////////////////////////////////////////////////////////////
4581 // keyboard mapping
4582 static const char *kmap (KeySym k, uint state) {
4583 const char *res = NULL;
4585 state &= ~Mod2Mask; // numlock
4586 for (int f = 0; f < keymap_used; ++f) {
4587 uint mask = keymap[f].mask;
4589 if (keymap[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) {
4590 //fprintf(stderr, "kp: %d\n", IS_SET(MODE_APPKEYPAD));
4591 if (!IS_SET(MODE_APPKEYPAD)) {
4592 if (!keymap[f].kp) {
4593 //fprintf(stderr, "nokp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4594 return keymap[f].str; // non-keypad hit
4596 continue;
4598 //fprintf(stderr, "kp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4599 if (keymap[f].kp) return keymap[f].str; // keypad hit
4600 res = keymap[f].str; // kp mode, but non-kp mapping found
4603 return res;
4607 static const char *kbind (KeySym k, uint state) {
4608 state &= ~Mod2Mask; // numlock
4609 for (int f = 0; f < keybinds_used; ++f) {
4610 uint mask = keybinds[f].mask;
4612 if (keybinds[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) return keybinds[f].str;
4614 return NULL;
4618 static KeySym do_keytrans (KeySym ks) {
4619 for (int f = 0; f < keytrans_used; ++f) if (keytrans[f].src == ks) return keytrans[f].dst;
4620 return ks;
4624 static void cmdline_closequeryexec (int cancelled) {
4625 if (!cancelled) {
4626 char *rep = term->cmdline+term->cmdreslen;
4628 trimstr(rep);
4629 if (!rep[0] || strchr("yt", tolower(rep[0])) != NULL) {
4630 closeRequestComes = 2;
4631 return;
4634 closeRequestComes = 0;
4638 static void kpress (XEvent *ev) {
4639 XKeyEvent *e = &ev->xkey;
4640 KeySym ksym = NoSymbol;
4641 const char *kstr;
4642 int len;
4643 Status status;
4644 char buf[32];
4646 if (term == NULL) return;
4648 if (!ptrBlanked && opt_ptrblank > 0) xblankPointer();
4650 //if (len < 1) len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
4651 if ((len = Xutf8LookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status)) > 0) buf[len] = 0;
4652 // leave only known mods
4653 e->state &= (Mod1Mask | Mod4Mask | ControlMask | ShiftMask);
4654 #ifdef DUMP_KEYSYMS
4656 const char *ksname = XKeysymToString(ksym);
4658 fprintf(stderr, "utf(%d):[%s] (%s) 0x%08x\n", len, len>=0?buf:"<shit>", ksname, (unsigned int)e->state);
4660 #endif
4661 if ((kstr = kbind(ksym, e->state)) != NULL) {
4662 // keybind found
4663 executeCommands(kstr);
4664 return;
4667 if (term->cmdMode != CMDMODE_NONE) {
4668 int mode = term->cmdMode;
4670 switch (do_keytrans(ksym)) {
4671 case XK_Return:
4672 tcmdlinehide();
4673 if (term->cmdexecfn) term->cmdexecfn(0);
4674 else if (mode == CMDMODE_INPUT) executeCommands(term->cmdline);
4675 break;
4676 case XK_BackSpace:
4677 if (mode == CMDMODE_INPUT) {
4678 tcmdlinechoplast();
4679 term->cmdtabpos = -1;
4680 term->cmdprevc = NULL;
4681 } else {
4682 tcmdlinehide();
4683 if (term->cmdexecfn) term->cmdexecfn(1);
4685 break;
4686 case XK_Escape:
4687 tcmdlinehide();
4688 if (term->cmdexecfn) term->cmdexecfn(1);
4689 break;
4690 case XK_Tab:
4691 if (mode == CMDMODE_INPUT && term->cmdline[0] && term->cmdcl == 0 && term->cmdexecfn == NULL) {
4692 const char *cpl;
4694 if (term->cmdtabpos < 0) {
4695 term->cmdtabpos = 0;
4696 while (term->cmdline[term->cmdtabpos] && isalnum(term->cmdline[term->cmdtabpos])) ++term->cmdtabpos;
4697 if (term->cmdline[term->cmdtabpos]) {
4698 term->cmdtabpos = -1;
4699 break;
4701 term->cmdprevc = NULL;
4703 cpl = findCommandCompletion(term->cmdline, term->cmdtabpos, term->cmdprevc);
4704 if (cpl == NULL && term->cmdprevc != NULL) cpl = findCommandCompletion(term->cmdline, term->cmdtabpos, NULL);
4705 term->cmdprevc = cpl;
4706 if (cpl != NULL) strcpy(term->cmdline, cpl);
4707 tcmdlinefixofs();
4708 } else if (mode != CMDMODE_INPUT) {
4709 tcmdlinehide();
4710 if (term->cmdexecfn) term->cmdexecfn(1);
4712 break;
4713 case XK_space:
4714 if (mode != CMDMODE_INPUT) {
4715 tcmdlinehide();
4716 break;
4718 // fallthru
4719 default:
4720 if (mode == CMDMODE_INPUT) {
4721 if (len > 0 && (unsigned char)buf[0] >= 32) {
4722 tcmdput(buf, len);
4723 term->cmdtabpos = -1;
4724 term->cmdprevc = NULL;
4727 break;
4729 return;
4732 if ((kstr = kmap(do_keytrans(ksym), e->state)) != NULL) {
4733 if (kstr[0]) {
4734 tunshowhistory();
4735 ttywritestr(kstr);
4737 } else {
4738 int meta = (e->state&Mod1Mask);
4740 int shift = (e->state&ShiftMask);
4741 int ctrl = (e->state&ControlMask);
4743 switch (ksym) {
4744 case XK_Return:
4745 tunshowhistory();
4746 if (meta) {
4747 ttywritestr("\x1b\x0a");
4748 } else {
4749 if (IS_SET(MODE_CRLF)) ttywritestr("\r\n"); else ttywritestr("\r");
4751 break;
4752 default:
4753 if (len > 0) {
4754 tunshowhistory();
4755 if (meta && len == 1) ttywritestr("\x1b");
4756 ttywrite(buf, len);
4758 break;
4764 ////////////////////////////////////////////////////////////////////////////////
4765 // xembed?
4766 static void cmessage (XEvent *e) {
4767 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
4768 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
4769 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
4770 xw.state |= WIN_FOCUSED;
4771 xseturgency(0);
4772 tsendfocusevent(1);
4773 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
4774 xw.state &= ~WIN_FOCUSED;
4775 tsendfocusevent(0);
4777 xdrawcursor();
4778 xdrawTabBar();
4779 xcopy(0, 0, term->col, term->row);
4780 return;
4783 if (e->xclient.data.l[0] == XA_WM_DELETE_WINDOW) {
4784 closeRequestComes = 1;
4785 return;
4790 ////////////////////////////////////////////////////////////////////////////////
4791 static void resize (XEvent *e) {
4792 int col, row;
4793 Term *ot = term;
4795 //if (e->xconfigure.width == 65535 || e->xconfigure.width == -1) e->xconfigure.width = xw.w;
4796 if (e->xconfigure.height == 65535 || e->xconfigure.height == -1) e->xconfigure.height = xw.h;
4797 //if ((short int)e->xconfigure.height < xw.ch) return;
4799 if (e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) return;
4800 xw.w = e->xconfigure.width;
4801 xw.h = e->xconfigure.height;
4802 col = xw.w/xw.cw;
4803 row = (xw.h-xw.tabheight)/xw.ch;
4804 //fprintf(stderr, "neww=%d; newh=%d; ch=%d; th=%d; col=%d; row=%d; ocol=%d; orow=%d\n", xw.w, xw.h, xw.ch, xw.tabheight, col, row, term->col, term->row);
4805 if (col == term->col && row == term->row) return;
4806 for (int f = 0; f < term_count; ++f) {
4807 term = term_array[f];
4808 if (tresize(col, row) && ot == term) draw(1);
4809 ttyresize();
4810 xresize(col, row);
4812 term = ot;
4813 XFreePixmap(xw.dpy, xw.pictab);
4814 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
4815 updateTabBar = 1;
4819 static inline int last_draw_too_old (void) {
4820 int tt = mclock_ticks();
4822 if (term != NULL) {
4823 if (term->dead || !term->wantRedraw) return 0;
4824 return (tt-term->lastDrawTime >= opt_drawtimeout);
4826 return (tt-lastDrawTime >= opt_maxdrawtimeout);
4830 ////////////////////////////////////////////////////////////////////////////////
4831 // main loop
4832 static void (*handler[LASTEvent])(XEvent *) = {
4833 [KeyPress] = kpress,
4834 [ClientMessage] = cmessage,
4835 [ConfigureNotify] = resize,
4836 [VisibilityNotify] = visibility,
4837 [UnmapNotify] = unmap,
4838 [Expose] = expose,
4839 [FocusIn] = focus,
4840 [FocusOut] = focus,
4841 [MotionNotify] = bmotion,
4842 [ButtonPress] = bpress,
4843 [ButtonRelease] = brelease,
4844 [SelectionNotify] = selnotify,
4845 [SelectionRequest] = selrequest,
4846 [SelectionClear] = selclear,
4850 static void run (void) {
4851 //int stuff_to_print = 0;
4852 int xfd = XConnectionNumber(xw.dpy);
4854 ptrLastMove = mclock_ticks();
4855 while (term_count > 0) {
4856 XEvent ev;
4857 fd_set rfd, wfd;
4858 struct timeval timeout;
4859 int maxfd = xfd;
4860 Term *ot;
4861 int dodraw;
4863 FD_ZERO(&rfd);
4864 FD_ZERO(&wfd);
4865 FD_SET(xfd, &rfd);
4866 //FD_SET(term->cmdfd, &rfd);
4867 // have something to write?
4868 for (int f = 0; f < term_count; ++f) {
4869 Term *t = term_array[f];
4871 if (!t->dead && term->cmdfd >= 0 && t->pid != 0) {
4872 if (t->cmdfd > maxfd) maxfd = t->cmdfd;
4873 FD_SET(t->cmdfd, &rfd);
4874 if (t->wrbufpos < t->wrbufused) FD_SET(t->cmdfd, &wfd);
4878 timeout.tv_sec = 0;
4879 timeout.tv_usec = (opt_drawtimeout+2)*1000;
4880 if (select(maxfd+1, &rfd, &wfd, NULL, &timeout) < 0) {
4881 if (errno == EINTR) continue;
4882 die("select failed: %s", SERRNO);
4885 ot = term;
4886 for (int f = 0; f < term_count; ++f) {
4887 Term *t = term_array[f];
4889 if (!t->dead && term->cmdfd >= 0 && term->pid != 0) {
4890 term = t;
4891 if (FD_ISSET(t->cmdfd, &wfd)) ttyflushwrbuf();
4892 if (FD_ISSET(t->cmdfd, &rfd)) ttyread(); //t->wantRedraw = 1;
4893 term = ot;
4897 termcleanup();
4898 if (term_count == 0) exit(exitcode);
4900 dodraw = 0;
4901 if (term != NULL && term->curblink > 0) {
4902 int tt = mclock_ticks();
4904 if (tt-term->lastBlinkTime >= term->curblink) {
4905 term->lastBlinkTime = tt;
4906 if ((xw.state&WIN_FOCUSED) || term->curblinkinactive) {
4907 term->curbhidden = (term->curbhidden ? 0 : -1);
4908 dodraw = 1;
4909 } else {
4910 term->curbhidden = 0;
4914 if (updateTabBar) xdrawTabBar();
4915 if (dodraw || last_draw_too_old()) draw(0);
4917 if (XPending(xw.dpy)) {
4918 while (XPending(xw.dpy)) {
4919 XNextEvent(xw.dpy, &ev);
4920 switch (ev.type) {
4921 //case VisibilityNotify:
4922 //case UnmapNotify:
4923 //case FocusIn:
4924 //case FocusOut:
4925 case MotionNotify:
4926 case ButtonPress:
4927 case ButtonRelease:
4928 xunblankPointer();
4929 ptrLastMove = mclock_ticks();
4930 break;
4931 default: ;
4933 if (XFilterEvent(&ev, xw.win)) continue;
4934 if (handler[ev.type]) (handler[ev.type])(&ev);
4938 if (term != NULL) {
4939 switch (closeRequestComes) {
4940 case 1: // just comes
4941 if (opt_ignoreclose == 0) {
4942 tcmdlinehide();
4943 tcmdlineinitex("Do you really want to close sterm [Y/n]? ");
4944 term->cmdexecfn = cmdline_closequeryexec;
4945 } else if (opt_ignoreclose < 0) {
4946 //FIXME: kill all clients?
4947 return;
4949 closeRequestComes = 0;
4950 break;
4951 case 2: // ok, die now
4952 //FIXME: kill all clients?
4953 return;
4957 if (!ptrBlanked && opt_ptrblank > 0 && mclock_ticks()-ptrLastMove >= opt_ptrblank) {
4958 xblankPointer();
4964 ////////////////////////////////////////////////////////////////////////////////
4965 typedef const char * (*IniHandlerFn) (const char *optname, const char *fmt, char *argstr, void *udata);
4968 typedef struct {
4969 const char *name;
4970 const char *fmt;
4971 void *udata;
4972 IniHandlerFn fn;
4973 } IniCommand;
4976 static const char *inifnGenericOneArg (const char *optname, const char *fmt, char *argstr, void *udata) {
4977 return iniParseArguments(argstr, fmt, udata);
4981 static const char *inifnGenericOneStr (const char *optname, const char *fmt, char *argstr, void *udata) {
4982 char *s = NULL;
4983 const char *err = iniParseArguments(argstr, fmt, &s);
4985 if (err != NULL) return err;
4986 if ((s = strdup(s)) == NULL) return "out of memory";
4987 if (udata) {
4988 char **ustr = (char **)udata;
4990 if (*ustr) free(*ustr);
4991 *ustr = s;
4993 return NULL;
4997 static const char *inifnTabPosition (const char *optname, const char *fmt, char *argstr, void *udata) {
4998 int newpos = -1;
4999 const char *err = NULL;
5001 while (argstr[0]) {
5002 char *s = NULL;
5004 if ((err = iniParseArguments(argstr, "R-", &argstr)) != NULL) return err;
5005 if (!argstr[0]) break;
5007 if ((err = iniParseArguments(argstr, "s!-R-", &s, &argstr)) != NULL) return err;
5008 if (tolower(s[0]) == 't') newpos = 1;
5009 else if (tolower(s[0]) == 'b') newpos = 0;
5010 else if ((err = iniParseArguments(s, fmt, &newpos)) != NULL) return err;
5013 if (newpos == -1) return "invalid tabbar position";
5014 opt_tabposition = newpos;
5015 return NULL;
5019 static const IniCommand iniCommands[] = {
5020 {"term", "s!-", &opt_term, inifnGenericOneStr},
5021 {"class", "s!-", &opt_class, inifnGenericOneStr},
5022 {"title", "s!-", &opt_title, inifnGenericOneStr},
5023 {"fontnorm", "s!-", &opt_fontnorm, inifnGenericOneStr},
5024 {"fontbold", "s!-", &opt_fontbold, inifnGenericOneStr},
5025 {"fonttab", "s!-", &opt_fonttab, inifnGenericOneStr},
5026 {"shell", "s!-", &opt_shell, inifnGenericOneStr},
5027 {"doubleclicktimeout", "i{0,10000}", &opt_doubleclick_timeout, inifnGenericOneArg},
5028 {"tripleclicktimeout", "i{0,10000}", &opt_tripleclick_timeout, inifnGenericOneArg},
5029 {"tabsize", "i{1,256}", &opt_tabsize, inifnGenericOneArg},
5030 {"defaultfg", "i{0,511}", &defaultFG, inifnGenericOneArg},
5031 {"defaultbg", "i{0,511}", &defaultBG, inifnGenericOneArg},
5032 {"defaultcursorfg", "i{0,511}", &defaultCursorFG, inifnGenericOneArg},
5033 {"defaultcursorbg", "i{0,511}", &defaultCursorBG, inifnGenericOneArg},
5034 {"defaultinactivecursorfg", "i{0,511}", &defaultCursorInactiveFG, inifnGenericOneArg},
5035 {"defaultinactivecursorbg", "i{-1,511}", &defaultCursorInactiveBG, inifnGenericOneArg},
5036 {"defaultboldfg", "i{-1,511}", &defaultBoldFG, inifnGenericOneArg},
5037 {"defaultunderlinefg", "i{-1,511}", &defaultUnderlineFG, inifnGenericOneArg},
5038 {"normaltabfg", "i{0,511}", &normalTabFG, inifnGenericOneArg},
5039 {"normaltabbg", "i{0,511}", &normalTabBG, inifnGenericOneArg},
5040 {"activetabfg", "i{0,511}", &activeTabFG, inifnGenericOneArg},
5041 {"activetabbg", "i{0,511}", &activeTabBG, inifnGenericOneArg},
5042 {"maxhistory", "i{0,65535}", &opt_maxhistory, inifnGenericOneArg},
5043 {"ptrblank", "i{0,65535}", &opt_ptrblank, inifnGenericOneArg},
5044 {"tabcount", "i{1,128}", &opt_tabcount, inifnGenericOneArg},
5045 {"tabposition", "i{0,1}", &opt_tabposition, inifnTabPosition},
5046 {"drawtimeout", "i{5,30000}", &opt_drawtimeout, inifnGenericOneArg},
5047 {"audiblebell", "b", &opt_audiblebell, inifnGenericOneArg},
5048 {"urgentbell", "b", &opt_urgentbell, inifnGenericOneArg},
5049 {"cursorblink", "i{0,10000}", &opt_cursorBlink, inifnGenericOneArg},
5050 {"cursorblinkinactive", "b", &opt_cursorBlinkInactive, inifnGenericOneArg},
5051 {"drawunderline", "b", &opt_drawunderline, inifnGenericOneArg},
5052 {"ignoreclose", "i{-1,1}", &opt_ignoreclose, inifnGenericOneArg},
5053 {"maxdrawtimeout", "i{100,60000}", &opt_maxdrawtimeout, inifnGenericOneArg},
5054 {NULL, NULL, NULL, NULL}
5058 #define MISC_CMD_NONE ((const char *)-1)
5061 // NULL: command processed; MISC_CMD_NONE: unknown command; !NULL: error
5062 static const char *processMiscCmds (const char *optname, char *argstr) {
5063 const char *err = NULL;
5065 if (strcasecmp(optname, "unimap") == 0) {
5066 int uni, ch;
5067 char *alt = NULL;
5069 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
5070 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
5071 if (unimap) free(unimap);
5072 unimap = NULL;
5073 return NULL;
5076 //unimap 0x2592 0x61 alt
5077 if ((err = iniParseArguments(argstr, "i{0,65535}i{0,255}|s!-", &uni, &ch, &alt)) != NULL) return err;
5078 if (alt != NULL && strcasecmp(alt, "alt") != 0) return "invalid unimap";
5079 if (unimap == NULL) {
5080 if ((unimap = calloc(65536, sizeof(unimap[0]))) == NULL) return "out of memory";
5082 if (alt != NULL && ch == 0) alt = NULL;
5083 if (alt != NULL && ch < 96) return "invalid unimap";
5084 unimap[uni] = ch;
5085 if (alt != NULL) unimap[uni] |= 0x8000;
5086 return NULL;
5089 if (strcasecmp(optname, "keytrans") == 0) {
5090 char *src = NULL, *dst = NULL;
5092 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
5093 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
5094 keytrans_reset();
5095 return NULL;
5098 if ((err = iniParseArguments(argstr, "s!-s!-", &src, &dst)) != NULL) return err;
5099 keytrans_add(src, dst);
5100 return NULL;
5103 if (strcasecmp(optname, "keybind") == 0) {
5104 char *key = NULL, *act = NULL;
5105 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
5106 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
5107 keybinds_reset();
5108 return NULL;
5111 if ((err = iniParseArguments(argstr, "s!-R!", &key, &act)) != NULL) return err;
5112 if (act[0] == '"') {
5113 char *t;
5115 if ((err = iniParseArguments(act, "s!", &t)) != NULL) return err;
5116 act = t;
5118 keybind_add(key, act);
5119 return NULL;
5122 if (strcasecmp(optname, "keymap") == 0) {
5123 char *key = NULL, *str = NULL;
5125 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
5126 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
5127 keymap_reset();
5128 return NULL;
5131 if ((err = iniParseArguments(argstr, "s!-s!-", &key, &str)) != NULL) return err;
5132 keymap_add(key, str);
5133 return NULL;
5136 return MISC_CMD_NONE;
5140 #define INI_LINE_SIZE (32768)
5142 // <0: file not found
5143 // >0: file loading error
5144 // 0: ok
5145 static int loadConfig (const char *fname) {
5146 int inifelse = 0; // 0: not; 1: doing true; 2: doing false; -1: waiting else/endif; -2: waiting endif
5147 FILE *fi = fopen(fname, "r");
5148 const char *err = NULL;
5149 char *line;
5150 int lineno = 0;
5152 if (fi == NULL) return -1;
5153 if ((line = malloc(INI_LINE_SIZE)) == NULL) { err = "out of memory"; goto quit; }
5155 while (fgets(line, INI_LINE_SIZE-1, fi) != NULL) {
5156 char *optname, *argstr, *d;
5157 int goodoption = 0;
5159 ++lineno;
5160 line[INI_LINE_SIZE-1] = 0;
5161 // get option name
5162 for (optname = line; *optname && isspace(*optname); ++optname) ;
5163 if (!optname[0] || optname[0] == '#') continue; // comment
5164 if (!isalnum(optname[0])) { err = "invalid option name"; goto quit; }
5165 d = argstr = optname;
5166 while (*argstr) {
5167 if (!argstr[0] || isspace(argstr[0])) break;
5168 if (!isalnum(argstr[0]) && argstr[0] != '_' && argstr[0] != '.') { err = "invalid option name"; goto quit; }
5169 if (argstr[0] != '_') *d++ = tolower(*argstr);
5170 ++argstr;
5172 if (*argstr) ++argstr;
5173 *d = 0;
5174 if (!isalnum(optname[0])) { err = "invalid option name"; goto quit; }
5176 if (strcasecmp(optname, "ifterm") == 0) {
5177 char *val;
5179 if (inifelse != 0) { err = "nested ifs are not allowed"; goto quit; }
5180 inifelse = -1;
5181 if ((err = iniParseArguments(argstr, "s", &val)) != NULL) goto quit;
5182 if (strcasecmp(opt_term, val) == 0) inifelse = 1;
5183 continue;
5185 if (strcasecmp(optname, "else") == 0) {
5186 switch (inifelse) {
5187 case -1: inifelse = 2; break;
5188 case 2: case -2: case 0: err = "else without if"; goto quit;
5189 case 1: inifelse = -2; break;
5191 continue;
5193 if (strcasecmp(optname, "endif") == 0) {
5194 switch (inifelse) {
5195 case -1: case -2: case 1: case 2: inifelse = 0; break;
5196 case 0: err = "endif without if"; goto quit;
5198 continue;
5201 if (inifelse < 0) {
5202 //trimstr(argstr);
5203 //fprintf(stderr, "skip: [%s]\n", argstr);
5204 continue;
5206 if (opt_term_locked && strcasecmp(optname, "term") == 0) continue; // termname given in command line
5207 // ok, we have option name in `optname` and arguments in `argstr`
5208 if (strncmp(optname, "color.", 6) == 0) {
5209 int n = 0;
5210 char *s = NULL;
5212 optname += 6;
5213 if (!optname[0]) { err = "invalid color option"; goto quit; }
5214 while (*optname) {
5215 if (!isdigit(*optname)) { err = "invalid color option"; goto quit; }
5216 n = (n*10)+(optname[0]-'0');
5217 ++optname;
5219 if (n < 0 || n > 511) { err = "invalid color index"; goto quit; }
5221 if ((err = iniParseArguments(argstr, "s!-", &s)) != NULL) goto quit;
5222 if ((s = strdup(s)) == NULL) { err = "out of memory"; goto quit; }
5223 if (opt_colornames[n] != NULL) free(opt_colornames[n]);
5224 opt_colornames[n] = s;
5225 continue;
5228 if ((err = processMiscCmds(optname, argstr)) != MISC_CMD_NONE) {
5229 if (err != NULL) goto quit;
5230 continue;
5231 } else {
5232 err = NULL;
5235 for (int f = 0; iniCommands[f].name != NULL; ++f) {
5236 if (strcmp(iniCommands[f].name, optname) == 0) {
5237 if ((err = iniCommands[f].fn(optname, iniCommands[f].fmt, argstr, iniCommands[f].udata)) != NULL) goto quit;
5238 goodoption = 1;
5239 break;
5242 if (!goodoption) {
5243 fprintf(stderr, "ini error at line %d: unknown option '%s'!\n", lineno, optname);
5246 quit:
5247 if (line != NULL) free(line);
5248 fclose(fi);
5249 if (err == NULL && inifelse != 0) err = "if without endif";
5250 if (err != NULL) die("ini error at line %d: %s", lineno, err);
5251 return 0;
5255 static void initDefaultOptions (void) {
5256 opt_title = strdup("sterm");
5257 opt_class = strdup("sterm");
5258 opt_term = strdup(TNAME);
5259 opt_fontnorm = strdup(FONT);
5260 opt_fontbold = strdup(FONTBOLD);
5261 opt_fonttab = strdup(FONTTAB);
5262 opt_shell = strdup(SHELL);
5264 memset(opt_colornames, 0, sizeof(opt_colornames));
5265 for (int f = 0; f < LEN(defcolornames); ++f) opt_colornames[f] = strdup(defcolornames[f]);
5266 for (int f = 0; f < LEN(defextcolornames); ++f) opt_colornames[f+256] = strdup(defextcolornames[f]);
5268 keytrans_add("KP_Home", "Home");
5269 keytrans_add("KP_Left", "Left");
5270 keytrans_add("KP_Up", "Up");
5271 keytrans_add("KP_Right", "Right");
5272 keytrans_add("KP_Down", "Down");
5273 keytrans_add("KP_Prior", "Prior");
5274 keytrans_add("KP_Next", "Next");
5275 keytrans_add("KP_End", "End");
5276 keytrans_add("KP_Begin", "Begin");
5277 keytrans_add("KP_Insert", "Insert");
5278 keytrans_add("KP_Delete", "Delete");
5280 keybind_add("shift+Insert", "PastePrimary");
5281 keybind_add("alt+Insert", "PasteCliboard");
5282 keybind_add("ctrl+alt+t", "NewTab");
5283 keybind_add("ctrl+alt+Left", "SwitchToTab prev");
5284 keybind_add("ctrl+alt+Right", "SwitchToTab next");
5286 keymap_add("BackSpace", "\177");
5287 keymap_add("Insert", "\x1b[2~");
5288 keymap_add("Delete", "\x1b[3~");
5289 keymap_add("Home", "\x1b[1~");
5290 keymap_add("End", "\x1b[4~");
5291 keymap_add("Prior", "\x1b[5~");
5292 keymap_add("Next", "\x1b[6~");
5293 keymap_add("F1", "\x1bOP");
5294 keymap_add("F2", "\x1bOQ");
5295 keymap_add("F3", "\x1bOR");
5296 keymap_add("F4", "\x1bOS");
5297 keymap_add("F5", "\x1b[15~");
5298 keymap_add("F6", "\x1b[17~");
5299 keymap_add("F7", "\x1b[18~");
5300 keymap_add("F8", "\x1b[19~");
5301 keymap_add("F9", "\x1b[20~");
5302 keymap_add("F10", "\x1b[21~");
5303 keymap_add("Up", "\x1bOA");
5304 keymap_add("Down", "\x1bOB");
5305 keymap_add("Right", "\x1bOC");
5306 keymap_add("Left", "\x1bOD");
5310 ////////////////////////////////////////////////////////////////////////////////
5311 static Term *oldTerm;
5312 static int oldTermIdx;
5313 static Term *newTerm;
5314 static int newTermIdx;
5315 static int newTermSwitch;
5318 typedef void (*CmdHandlerFn) (const char *cmdname, char *argstr);
5320 typedef struct {
5321 const char *name;
5322 CmdHandlerFn fn;
5323 } Command;
5326 static void cmdPastePrimary (const char *cmdname, char *argstr) {
5327 selpaste(XA_PRIMARY);
5331 static void cmdPasteSecondary (const char *cmdname, char *argstr) {
5332 selpaste(XA_SECONDARY);
5336 static void cmdPasteClipboard (const char *cmdname, char *argstr) {
5337 selpaste(XA_CLIPBOARD);
5341 static void cmdExec (const char *cmdname, char *argstr) {
5342 if (term != NULL) {
5343 if (term->execcmd != NULL) free(term->execcmd);
5344 term->execcmd = (argstr[0] ? strdup(argstr) : NULL);
5349 static int parseTabArgs (char *argstr, int *noswitch, int nowrap, int idx) {
5350 for (;;) {
5351 char *arg;
5353 while (*argstr && isspace(*argstr)) ++argstr;
5354 if (!argstr[0]) break;
5355 if (iniParseArguments(argstr, "s-R-", &arg, &argstr) != NULL) break;
5357 if (strcasecmp(arg, "noswitch") == 0) *noswitch = 1;
5358 else if (strcasecmp(arg, "switch") == 0) *noswitch = 0;
5359 else if (strcasecmp(arg, "nowrap") == 0) nowrap = 1;
5360 else if (strcasecmp(arg, "wrap") == 0) nowrap = 0;
5361 else if (strcasecmp(arg, "first") == 0) idx = 0;
5362 else if (strcasecmp(arg, "last") == 0) idx = term_count-1;
5363 else if (strcasecmp(arg, "prev") == 0) idx = -1;
5364 else if (strcasecmp(arg, "next") == 0) idx = -2;
5365 else {
5366 long int n = -1;
5367 char *eptr;
5369 n = strtol(arg, &eptr, 0);
5370 if (!eptr[0] && n >= 0 && n < term_count) idx = n;
5373 switch (idx) {
5374 case -1: // prev
5375 if ((idx = termidx-1) < 0) idx = nowrap ? 0 : term_count-1;
5376 break;
5377 case -2: // next
5378 if ((idx = termidx+1) >= term_count) idx = nowrap ? term_count-1 : 0;
5379 break;
5381 return idx;
5385 static void flushNewTerm (void) {
5386 if (newTerm != NULL) {
5387 if (newTermSwitch && term != NULL) term->lastActiveTime = mclock_ticks();
5388 term = newTerm;
5389 termidx = newTermIdx;
5390 tinitialize(term_array[0]->col, term_array[0]->row);
5392 term->picbufh = term->row*xw.ch;
5393 term->picbufw = term->col*xw.cw;
5394 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
5395 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
5397 if (ttynew(term) != 0) {
5398 term = oldTerm;
5399 termidx = oldTermIdx;
5400 termfree(newTermIdx);
5401 } else {
5402 selinit();
5403 ttyresize();
5404 if (newTermSwitch) {
5405 term = NULL;
5406 termidx = 0;
5407 switchToTerm(newTermIdx, 1);
5408 oldTerm = term;
5409 oldTermIdx = termidx;
5410 } else {
5411 term = oldTerm;
5412 termidx = oldTermIdx;
5415 newTerm = NULL;
5420 static void cmdNewTab (const char *cmdname, char *argstr) {
5421 int noswitch = 0, idx;
5423 if (opt_disabletabs) return;
5424 flushNewTerm();
5425 if ((newTerm = termalloc()) == NULL) return;
5426 /*idx =*/ parseTabArgs(argstr, &noswitch, 0, termidx);
5427 idx = term_count-1;
5428 if (!noswitch) {
5429 if (term != NULL) term->lastActiveTime = mclock_ticks();
5430 oldTermIdx = idx;
5432 newTermIdx = termidx = idx;
5433 term = newTerm;
5434 newTermSwitch = !noswitch;
5438 static void cmdCloseTab (const char *cmdname, char *argstr) {
5439 flushNewTerm();
5440 if (term != NULL && !term->dead) kill(term->pid, SIGTERM);
5444 static void cmdKillTab (const char *cmdname, char *argstr) {
5445 flushNewTerm();
5446 if (!term->dead) kill(term->pid, SIGKILL);
5450 static void cmdSwitchToTab (const char *cmdname, char *argstr) {
5451 int noswitch = 0, idx;
5453 flushNewTerm();
5454 idx = parseTabArgs(argstr, &noswitch, 0, -666);
5455 if (idx >= 0) {
5456 switchToTerm(idx, 1);
5457 oldTerm = term;
5458 oldTermIdx = termidx;
5463 static void cmdMoveTabTo (const char *cmdname, char *argstr) {
5464 int noswitch = 0, idx;
5466 flushNewTerm();
5467 idx = parseTabArgs(argstr, &noswitch, 0, termidx);
5468 if (idx != termidx && idx >= 0 && idx < term_count) {
5469 Term *t = term_array[termidx];
5471 // remove current term
5472 for (int f = termidx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
5473 // insert term
5474 for (int f = term_count-2; f >= idx; --f) term_array[f+1] = term_array[f];
5475 term_array[idx] = t;
5476 termidx = idx;
5477 oldTerm = t;
5478 oldTermIdx = idx;
5479 updateTabBar = 1;
5484 static void cmdDefaultFG (const char *cmdname, char *argstr) {
5485 char *s = NULL;
5486 int c;
5488 if (iniParseArguments(argstr, "i{0,511}|s-", &c, &s) == NULL) {
5489 if (s != NULL && tolower(s[0]) == 'g') defaultFG = c; else term->deffg = c;
5494 static void cmdDefaultBG (const char *cmdname, char *argstr) {
5495 char *s = NULL;
5496 int c;
5498 if (iniParseArguments(argstr, "i{0,511}|s-", &c) == NULL) {
5499 if (s != NULL && tolower(s[0]) == 'g') {
5500 defaultBG = c;
5501 } else {
5502 term->defbg = c;
5503 XSetWindowBackground(xw.dpy, xw.win, getColor(term->defbg));
5504 if (newTerm == NULL) {
5505 tfulldirt();
5506 draw(1);
5507 xclearunused();
5514 static void scrollHistory (int delta) {
5515 if (term->maxhistory < 1) return; // no history
5516 term->topline += delta;
5517 if (term->topline > term->maxhistory) term->topline = term->maxhistory;
5518 if (term->topline < 0) term->topline = 0;
5519 tfulldirt();
5520 draw(1);
5524 static void cmdScrollHistoryLineUp (const char *cmdname, char *argstr) {
5525 scrollHistory(1);
5529 static void cmdScrollHistoryPageUp (const char *cmdname, char *argstr) {
5530 scrollHistory(term->row);
5534 static void cmdScrollHistoryLineDown (const char *cmdname, char *argstr) {
5535 scrollHistory(-1);
5539 static void cmdScrollHistoryPageDown (const char *cmdname, char *argstr) {
5540 scrollHistory(-term->row);
5544 static void cmdScrollHistoryTop (const char *cmdname, char *argstr) {
5545 scrollHistory(term->linecount);
5549 static void cmdScrollHistoryBottom (const char *cmdname, char *argstr) {
5550 scrollHistory(-term->linecount);
5554 static void cmdUTF8Locale (const char *cmdname, char *argstr) {
5555 int b;
5557 if (iniParseArguments(argstr, "b", &b) == NULL) {
5558 if (!needConversion) b = 1;
5559 if (term != NULL) term->needConv = !b;
5560 //fprintf(stderr, "needConv: %d (%d)\n", term->needConv, needConversion);
5565 static void cmdCommandMode (const char *cmdname, char *argstr) {
5566 if (term != NULL) {
5567 if (term->cmdMode == CMDMODE_NONE) tcmdlineinit(); else tcmdlinehide();
5572 // [show|hide]
5573 static void cmdCursor (const char *cmdname, char *argstr) {
5574 if (term != NULL) {
5575 char *s;
5577 if (iniParseArguments(argstr, "s!-", &s) != NULL) {
5578 tcmdlinemsg((term->c.state&CURSOR_HIDE) ? "cursor is hidden" : "cursor is visible");
5579 } else {
5580 if (strcasecmp(s, "show") == 0) term->c.state &= ~CURSOR_HIDE;
5581 else if (strcasecmp(s, "hide") == 0) term->c.state |= CURSOR_HIDE;
5582 term->wantRedraw = 1;
5588 static void cmdResetAttrs (const char *cmdname, char *argstr) {
5589 if (term != NULL) {
5590 term->c.attr.attr &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
5591 term->c.attr.attr |= ATTR_DEFFG | ATTR_DEFBG;
5592 term->c.attr.fg = term->deffg;
5593 term->c.attr.bg = term->defbg;
5598 static void cmdResetCharset (const char *cmdname, char *argstr) {
5599 if (term != NULL) {
5600 term->mode &= ~(MODE_GFX0|MODE_GFX1);
5601 term->charset = MODE_GFX0;
5606 // [norm|alt]
5607 static void cmdScreen (const char *cmdname, char *argstr) {
5608 if (term != NULL) {
5609 char *s;
5611 if (iniParseArguments(argstr, "s!-", &s) != NULL) {
5612 tcmdlinemsg(IS_SET(MODE_ALTSCREEN) ? "screen: alt" : "screen: norm");
5613 } else {
5614 if (strcasecmp(s, "norm") == 0) {
5615 if (IS_SET(MODE_ALTSCREEN)) tswapscreen();
5616 } else if (strcasecmp(s, "alt") == 0) {
5617 if (!IS_SET(MODE_ALTSCREEN)) tswapscreen();
5624 // [on|off]
5625 static void cmdMouseReports (const char *cmdname, char *argstr) {
5626 if (term != NULL) {
5627 int b;
5629 if (iniParseArguments(argstr, "b", &b) != NULL) {
5630 tcmdlinemsg(IS_SET(MODE_MOUSE) ? "mouse reports are on" : "mouse reports are off");
5631 } else {
5632 if (b) term->mode |= MODE_MOUSEBTN; else term->mode &= ~MODE_MOUSEBTN;
5638 static int cmd_parseIntArg (const char *fmt, char *argstr, int *b, int *global, int *toggle) {
5639 while (argstr[0]) {
5640 char *s = NULL;
5642 if (iniParseArguments(argstr, "R-", &argstr) != NULL) break;
5643 if (!argstr[0]) break;
5645 if (iniParseArguments(argstr, "s!-R-", &s, &argstr) != NULL) break;
5646 if (global && tolower(s[0]) == 'g') {
5647 *global = 1;
5648 } else if (toggle && tolower(s[0]) == 't') {
5649 *toggle = 1;
5650 } else {
5651 if (!b || iniParseArguments(s, fmt, b) != NULL) return -1;
5654 return 0;
5658 static void cmdAudibleBell (const char *cmdname, char *argstr) {
5659 int b = -1, toggle = 0, global = 0;
5661 if (term == NULL) return;
5662 if (cmd_parseIntArg("b", argstr, &b, &global, &toggle) != 0) return;
5664 if (b == -1) {
5665 tcmdlinemsg((global ? opt_audiblebell : (term->belltype&BELL_AUDIO)) ? "AudibleBell: 1" : "AudibleBell: 0");
5666 } else {
5667 if (toggle) {
5668 if (global) opt_audiblebell = !opt_audiblebell; else term->belltype ^= BELL_AUDIO;
5669 } else {
5670 if (global) opt_audiblebell = b; else term->belltype = (term->belltype&~BELL_AUDIO)|(b!=0?BELL_AUDIO:0);
5676 static void cmdUrgentBell (const char *cmdname, char *argstr) {
5677 int b = -1, toggle = 0, global = 0;
5679 if (term == NULL) return;
5680 if (cmd_parseIntArg("b", argstr, &b, &global, &toggle) != 0) return;
5682 if (b == -1) {
5683 tcmdlinemsg((global ? opt_urgentbell : (term->belltype&BELL_URGENT)) ? "UrgentBell: 1" : "UrgentBell: 0");
5684 } else {
5685 if (toggle) {
5686 if (global) opt_urgentbell = !opt_urgentbell; else term->belltype ^= BELL_URGENT;
5687 } else {
5688 if (global) opt_urgentbell = b; else term->belltype = (term->belltype&~BELL_URGENT)|(b!=0?BELL_URGENT:0);
5694 static void cmdMonochrome (const char *cmdname, char *argstr) {
5695 int b = -1, global = 0;
5697 if (term == NULL) return;
5698 if (cmd_parseIntArg("i{0,3}", argstr, &b, &global, NULL) != 0) return;
5700 if (b == -1) {
5701 char buf[32];
5703 b = (global ? globalBW : term->blackandwhite);
5704 sprintf(buf, "Monochrome: %d", b);
5705 tcmdlinemsg(buf);
5706 } else {
5707 if (global) {
5708 if (b == 3) tcmdlinemsg("Monochrome-global can't be '3'!");
5709 else globalBW = b;
5710 } else {
5711 term->blackandwhite = b;
5714 tfulldirt();
5715 updateTabBar = 1;
5719 static void cmdCursorBlink (const char *cmdname, char *argstr) {
5720 int b = -1, global = 0;
5722 if (term == NULL) return;
5723 if (cmd_parseIntArg("i{0,10000}", argstr, &b, &global, NULL) != 0) return;
5725 if (b == -1) {
5726 char buf[32];
5728 b = (global ? opt_cursorBlink : term->curblink);
5729 sprintf(buf, "CursorBlink: %d", b);
5730 tcmdlinemsg(buf);
5731 } else {
5732 if (global) {
5733 opt_cursorBlink = b;
5734 } else {
5735 term->curblink = b;
5736 term->curbhidden = 0;
5739 tfulldirt();
5740 updateTabBar = 1;
5744 static void cmdCursorBlinkInactive (const char *cmdname, char *argstr) {
5745 int b = -1, global = 0, toggle = 0, *iptr;
5747 if (term == NULL) return;
5748 if (cmd_parseIntArg("b", argstr, &b, &global, &toggle) != 0) return;
5750 iptr = (global ? &opt_cursorBlinkInactive : &term->curblinkinactive);
5751 if (b != -1) {
5752 if (toggle) *iptr = !(*iptr); else *iptr = b;
5753 draw(0);
5758 static void cmdIgnoreClose (const char *cmdname, char *argstr) {
5759 int b = -666;
5761 if (term == NULL) return;
5762 if (cmd_parseIntArg("i{-1,1}", argstr, &b, NULL, NULL) != 0) return;
5764 if (b == -666) {
5765 char buf[32];
5767 sprintf(buf, "IgnoreClose: %d", opt_ignoreclose);
5768 tcmdlinemsg(buf);
5769 } else {
5770 opt_ignoreclose = b;
5775 static void cmdMaxHistory (const char *cmdname, char *argstr) {
5776 int b = -1, global = 0;
5778 if (term == NULL) return;
5779 if (cmd_parseIntArg("i{0,65535}", argstr, &b, &global, NULL) != 0) return;
5781 if (b == -1) {
5782 char buf[32];
5784 sprintf(buf, "MaxHistory: %d", (global?opt_maxhistory:term->maxhistory));
5785 tcmdlinemsg(buf);
5786 } else {
5787 if (!global) tadjustmaxhistory(b); else opt_maxhistory = b;
5792 static void cmdMaxDrawTimeout (const char *cmdname, char *argstr) {
5793 int b = -1;
5795 if (term == NULL) return;
5796 if (cmd_parseIntArg("i{100,60000}", argstr, &b, NULL, NULL) != 0) return;
5798 if (b == -1) {
5799 char buf[32];
5801 sprintf(buf, "MaxDrawTimeout: %d", opt_maxdrawtimeout);
5802 tcmdlinemsg(buf);
5803 } else {
5804 opt_maxdrawtimeout = b;
5809 static void cmdTabPosition (const char *cmdname, char *argstr) {
5810 int newpos = -1;
5812 while (argstr[0]) {
5813 char *s = NULL;
5815 if (iniParseArguments(argstr, "R-", &argstr) != NULL) break;
5816 if (!argstr[0]) break;
5818 if (iniParseArguments(argstr, "s!-R-", &s, &argstr) != NULL) break;
5819 if (tolower(s[0]) == 't') newpos = 1;
5820 else if (tolower(s[0]) == 'b') newpos = 0;
5821 else if (iniParseArguments(s, "i{0,1}", &newpos) != NULL) return;
5824 if (newpos == -1) {
5825 char buf[32];
5827 sprintf(buf, "TabPostion: %s", (opt_tabposition == 0 ? "bottom" : "top"));
5828 tcmdlinemsg(buf);
5829 } else if (opt_tabposition != newpos) {
5830 opt_tabposition = newpos;
5831 updateTabBar = 1;
5832 tfulldirt();
5833 draw(1);
5834 xclearunused();
5839 static void cmdTabCount (const char *cmdname, char *argstr) {
5840 int b = -1;
5842 if (term == NULL) return;
5843 if (cmd_parseIntArg("i{1,128}", argstr, &b, NULL, NULL) != 0) return;
5845 if (b == -1) {
5846 char buf[32];
5848 sprintf(buf, "TabCount: %d", opt_tabcount);
5849 tcmdlinemsg(buf);
5850 } else if (opt_tabcount != b) {
5851 opt_tabcount = b;
5852 fixFirstTab();
5853 xdrawTabBar();
5858 static void cmdDoFullRedraw (const char *cmdname, char *argstr) {
5859 updateTabBar = 1;
5860 tfulldirt();
5861 xclearunused();
5862 draw(1);
5866 static void cmdSetTitle (const char *cmdname, char *argstr) {
5867 if (term != NULL) {
5868 char *s = NULL;
5870 if (iniParseArguments(argstr, "s-", &s) != NULL || s == NULL) return;
5871 memset(term->title, 0, sizeof(term->title));
5872 while (strlen(s) > ESC_TITLE_SIZ) utf8choplast(s);
5873 fprintf(stderr, "[%s]\n", s);
5874 strncpy(term->title, s, ESC_TITLE_SIZ);
5875 fixWindowTitle(term);
5876 updateTabBar = 1;
5877 xdrawTabBar();
5882 static const Command commandList[] = {
5883 {"PastePrimary", cmdPastePrimary},
5884 {"PasteSecondary", cmdPasteSecondary},
5885 {"PasteClipboard", cmdPasteClipboard},
5886 {"exec", cmdExec},
5887 {"NewTab", cmdNewTab}, // 'noswitch' 'next' 'prev' 'first' 'last'
5888 {"CloseTab", cmdCloseTab},
5889 {"KillTab", cmdKillTab},
5890 {"SwitchToTab", cmdSwitchToTab}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5891 {"MoveTabTo", cmdMoveTabTo}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5892 {"DefaultFG", cmdDefaultFG},
5893 {"DefaultBG", cmdDefaultBG},
5894 {"ScrollHistoryLineUp", cmdScrollHistoryLineUp},
5895 {"ScrollHistoryPageUp", cmdScrollHistoryPageUp},
5896 {"ScrollHistoryLineDown", cmdScrollHistoryLineDown},
5897 {"ScrollHistoryPageDown", cmdScrollHistoryPageDown},
5898 {"ScrollHistoryTop", cmdScrollHistoryTop},
5899 {"ScrollHistoryBottom", cmdScrollHistoryBottom},
5900 {"UTF8Locale", cmdUTF8Locale}, // 'on', 'off'
5901 {"AudibleBell", cmdAudibleBell},
5902 {"UrgentBell", cmdUrgentBell},
5903 {"CommandMode", cmdCommandMode},
5904 {"Cursor", cmdCursor},
5905 {"ResetAttrs", cmdResetAttrs},
5906 {"ResetCharset", cmdResetCharset},
5907 {"Screen", cmdScreen},
5908 {"MouseReports", cmdMouseReports},
5909 {"Monochrome", cmdMonochrome},
5910 {"Mono", cmdMonochrome},
5911 {"CursorBlink", cmdCursorBlink},
5912 {"CursorBlinkInactive", cmdCursorBlinkInactive},
5913 {"IgnoreClose", cmdIgnoreClose},
5914 {"MaxHistory", cmdMaxHistory},
5915 {"MaxDrawTimeout", cmdMaxDrawTimeout},
5916 {"TabPosition", cmdTabPosition},
5917 {"TabCount", cmdTabCount},
5918 {"DoFullRedraw", cmdDoFullRedraw},
5919 {"SetTitle", cmdSetTitle},
5921 {"KeyBind", NULL},
5922 {"KeyTrans", NULL},
5923 {"KeyMap", NULL},
5924 {"UniMap", NULL},
5926 {"term", cmdTermName},
5927 {"title", cmdWinTitle},
5928 {"tabsize", cmdTabSize},
5929 {"defaultcursorfg", cmdDefaultCursorFG},
5930 {"defaultcursorbg", cmdDefaultCursorBG},
5931 {"defaultinactivecursorfg", cmdDefaultInactiveCursorFG},
5932 {"defaultinactivecursorbg", cmdDefaultInactiveCursorBG},
5933 {"defaultboldfg", cmdDefaultBoldFG},
5934 {"defaultunderlinefg", cmdDefaultUnderlineFG},
5936 {NULL, NULL}
5940 static const char *findCommandCompletion (const char *str, int slen, const char *prev) {
5941 const char *res = NULL;
5942 int phit = 0;
5944 if (slen < 1) return NULL;
5945 for (int f = 0; commandList[f].name != NULL; ++f) {
5946 if (strlen(commandList[f].name) >= slen && strncasecmp(commandList[f].name, str, slen) == 0) {
5947 if (prev == NULL || phit) return commandList[f].name;
5948 if (strcasecmp(commandList[f].name, prev) == 0) phit = 1;
5949 if (res == NULL) res = commandList[f].name;
5952 return res;
5956 // !0: NewTab command
5957 static int executeCommand (const char *str, int slen) {
5958 const char *e;
5959 char *cmdname;
5960 int cmdfound = 0;
5962 if (str == NULL) return 0;
5963 if (slen < 0) slen = strlen(str);
5965 for (int f = 0; f < slen; ++f) if (!str[f]) { slen = f; break; }
5967 while (slen > 0 && isspace(*str)) { ++str; --slen; }
5968 if (slen < 1 || !str[0]) return 0;
5970 for (e = str; slen > 0 && !isspace(*e); ++e, --slen) ;
5972 if (e-str > 127) return 0;
5973 cmdname = alloca(e-str+8);
5974 if (cmdname == NULL) return 0;
5975 memcpy(cmdname, str, e-str);
5976 cmdname[e-str] = 0;
5977 if (opt_disabletabs && strcasecmp(cmdname, "NewTab") == 0) return 1;
5979 while (slen > 0 && isspace(*e)) { ++e; --slen; }
5980 //FIXME: ugly copypaste!
5982 for (int f = 0; commandList[f].name != NULL; ++f) {
5983 if (strcasecmp(commandList[f].name, cmdname) == 0 && commandList[f].fn != NULL) {
5984 char *left = calloc(slen+2, 1);
5986 if (left != NULL) {
5987 if (slen > 0) memcpy(left, e, slen);
5988 //fprintf(stderr, "command: [%s]; args: [%s]\n", cmdname, left);
5989 commandList[f].fn(cmdname, left);
5990 free(left);
5992 cmdfound = 1;
5993 break;
5997 if (!cmdfound) {
5998 char *left = calloc(slen+2, 1);
6000 if (left != NULL) {
6001 if (slen > 0) memcpy(left, e, slen);
6002 processMiscCmds(cmdname, left);
6003 free(left);
6007 return 0;
6012 static const char *cmdpSkipStr (const char *str) {
6013 while (*str && isspace(*str)) ++str;
6014 if (str[0] && str[0] != ';') {
6015 char qch = ' ';
6017 while (*str) {
6018 if (*str == ';' && qch == ' ') break;
6019 if (qch != ' ' && *str == qch) { qch = ' '; ++str; continue; }
6020 if (*str == '"' || *str == '\'') {
6021 if (qch == ' ') qch = *str;
6022 ++str;
6023 continue;
6025 if (*str++ == '\\' && *str) ++str;
6028 return str;
6033 static void executeCommands (const char *str) {
6034 oldTerm = term;
6035 oldTermIdx = termidx;
6036 newTerm = NULL;
6037 newTermSwitch = 0;
6038 if (str == NULL) return;
6039 while (*str) {
6040 const char *ce;
6041 char qch;
6043 while (*str && isspace(*str)) ++str;
6044 if (!*str) break;
6045 if (*str == ';') { ++str; continue; }
6047 ce = str;
6048 qch = ' ';
6049 while (*ce) {
6050 if (*ce == ';' && qch == ' ') break;
6051 if (qch != ' ' && *ce == qch) { qch = ' '; ++ce; continue; }
6052 if (*ce == '"' || *ce == '\'') {
6053 if (qch == ' ') qch = *ce;
6054 ++ce;
6055 continue;
6057 if (*ce++ == '\\' && *ce) ++ce;
6060 if (executeCommand(str, ce-str)) break;
6061 if (*ce) str = ce+1; else break;
6063 flushNewTerm();
6064 switchToTerm(oldTermIdx, 1);
6068 ////////////////////////////////////////////////////////////////////////////////
6069 int main (int argc, char *argv[]) {
6070 char *configfile = NULL;
6071 char *runcmd = NULL;
6073 //dbgLogInit();
6075 for (int f = 1; f < argc; f++) {
6076 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
6077 if (strcmp(argv[f], "-into") == 0) { ++f; continue; }
6078 if (strcmp(argv[f], "-embed") == 0) { ++f; continue; }
6079 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
6080 case 'e': f = argc+1; break;
6081 case 't':
6082 case 'c':
6083 case 'w':
6084 case 'b':
6085 case 'R':
6086 ++f;
6087 break;
6088 case 'T':
6089 if (++f < argc) {
6090 free(opt_term);
6091 opt_term = strdup(argv[f]);
6092 opt_term_locked = 1;
6094 break;
6095 case 'C':
6096 if (++f < argc) {
6097 if (configfile != NULL) free(configfile);
6098 configfile = strdup(argv[f]);
6100 break;
6101 case 'l':
6102 if (++f < argc) cliLocale = argv[f];
6103 break;
6104 case 'S': // single-tab mode
6105 opt_disabletabs = 1;
6106 break;
6107 case 'v':
6108 case 'h':
6109 default:
6110 fprintf(stderr, "%s", USAGE);
6111 exit(EXIT_FAILURE);
6115 initDefaultOptions();
6116 if (configfile == NULL) {
6117 const char *home = getenv("HOME");
6119 if (home != NULL) {
6120 configfile = SPrintf("%s/.sterm.rc", home);
6121 if (loadConfig(configfile) == 0) goto cfgdone;
6122 free(configfile); configfile = NULL;
6124 configfile = SPrintf("%s/.config/sterm.rc", home);
6125 if (loadConfig(configfile) == 0) goto cfgdone;
6126 free(configfile); configfile = NULL;
6129 configfile = SPrintf("/etc/sterm.rc");
6130 if (loadConfig(configfile) == 0) goto cfgdone;
6131 free(configfile); configfile = NULL;
6133 configfile = SPrintf("/etc/sterm/sterm.rc");
6134 if (loadConfig(configfile) == 0) goto cfgdone;
6135 free(configfile); configfile = NULL;
6137 configfile = SPrintf("./.sterm.rc");
6138 if (loadConfig(configfile) == 0) goto cfgdone;
6139 free(configfile); configfile = NULL;
6140 // no config
6141 } else {
6142 if (loadConfig(configfile) < 0) die("config file '%s' not found!", configfile);
6144 cfgdone:
6145 if (configfile != NULL) free(configfile); configfile = NULL;
6147 for (int f = 1; f < argc; f++) {
6148 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
6149 if (strcmp(argv[f], "-into") == 0 || strcmp(argv[f], "-embed") == 0) {
6150 if (opt_embed) free(opt_embed);
6151 opt_embed = strdup(argv[f]);
6152 continue;
6154 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
6155 case 't':
6156 if (++f < argc) {
6157 free(opt_title);
6158 opt_title = strdup(argv[f]);
6160 break;
6161 case 'c':
6162 if (++f < argc) {
6163 free(opt_class);
6164 opt_class = strdup(argv[f]);
6166 break;
6167 case 'w':
6168 if (++f < argc) {
6169 if (opt_embed) free(opt_embed);
6170 opt_embed = strdup(argv[f]);
6172 break;
6173 case 'R':
6174 if (++f < argc) runcmd = argv[f];
6175 break;
6176 case 'e':
6177 /* eat every remaining arguments */
6178 if (++f < argc) opt_cmd = &argv[f];
6179 f = argc+1;
6180 case 'T':
6181 case 'C':
6182 case 'l':
6183 ++f;
6184 break;
6185 case 'S':
6186 break;
6187 case 'v':
6188 case 'h':
6189 default:
6190 fprintf(stderr, "%s", USAGE);
6191 exit(EXIT_FAILURE);
6195 setenv("TERM", opt_term, 1);
6196 mclock_init();
6197 setlocale(LC_ALL, "");
6198 initLCConversion();
6199 updateTabBar = 1;
6200 termidx = 0;
6201 term = termalloc();
6202 if (term->execcmd != NULL) { free(term->execcmd); term->execcmd = NULL; }
6203 tinitialize(80, 25);
6204 if (ttynew(term) != 0) die("can't run process");
6205 opt_cmd = NULL;
6206 xinit();
6207 selinit();
6208 if (runcmd != NULL) executeCommands(runcmd);
6209 run();
6210 return 0;