fixed bug with mouse reporting (mouse coords was double-encoded)
[k8sterm.git] / src / sterm.c
blob78b8167d914dd76df7354707efec2d3264e98d88
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 <stdint.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <strings.h>
23 #include <signal.h>
24 #include <sys/ioctl.h>
25 #include <sys/select.h>
26 #include <sys/stat.h>
27 #include <sys/time.h>
28 #include <sys/types.h>
29 #include <sys/wait.h>
30 #include <time.h>
31 #include <unistd.h>
32 #include <X11/Xatom.h>
33 #include <X11/Xlib.h>
34 #include <X11/Xutil.h>
35 #include <X11/cursorfont.h>
36 #include <X11/keysym.h>
38 //#include "dbglog.h"
41 // uncomment the following to use XCreateGlyphCursor() instead of XCreatePixmapCursor()
42 //#define BLANKPTR_USE_GLYPH_CURSOR
45 //#define PASTE_SELECTION_DEBUG
47 //#define DUMP_KEYSYMS
49 //#define KEYPAD_DUMP
51 //#define DUMP_PROG_OUTPUT
52 //#define DUMP_PROG_INPUT
54 //#define DUMP_IO_READ
55 //#define DUMP_IO_WRITE
57 #if defined(DUMP_IO_READ) || defined(DUMP_IO_WRITE)
58 # define DUMP_IO
59 #endif
61 #ifdef KEYPAD_DUMP
62 # define DUMP_KEYPAD_SWITCH(strflag,strstate) do { fprintf(stderr, "KEYPAD %s (%s)\n", (strstate), (strflag)); } while (0)
63 #else
64 # define DUMP_KEYPAD_SWITCH(strflag,strstate) ((void)(sizeof(strflag)+sizeof(strstate)))
65 #endif
68 ////////////////////////////////////////////////////////////////////////////////
69 #define USAGE \
70 "sterm " VERSION " (c) 2010-2012 st engineers and Ketmar // Vampire Avalon\n" \
71 "usage: sterm [-t title] [-c class] [-w windowid] [-T termname] [-C config_file] [-l langiconv] [-S] [-v] [-R stcmd] [-e command...]\n"
74 ////////////////////////////////////////////////////////////////////////////////
75 #define FONT "-*-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"
76 #define FONTBOLD "-*-fixed-bold-r-normal-*-18-*-*-*-*-*-*-*"
77 #define FONTTAB "-*-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*"
80 /* Default shell to use if SHELL is not set in the env */
81 #define SHELL "/bin/sh"
84 /* Terminal colors (16 first used in escape sequence) */
85 static const char *defcolornames[] = {
86 #if 1
87 /* 8 normal colors */
88 "black",
89 "red3",
90 "green3",
91 "yellow3",
92 "blue2",
93 "magenta3",
94 "cyan3",
95 "gray90",
96 /* 8 bright colors */
97 "gray50",
98 "red",
99 "green",
100 "yellow",
101 "#5c5cff",
102 "magenta",
103 "cyan",
104 "white",
105 #else
106 /* 8 normal colors */
107 "#000000",
108 "#b21818",
109 "#18b218",
110 "#b26818",
111 "#1818b2",
112 "#b218b2",
113 "#18b2b2",
114 "#b2b2b2",
115 /* 8 bright colors */
116 "#686868",
117 "#ff5454",
118 "#54ff54",
119 "#ffff54",
120 "#5454ff",
121 "#ff54ff",
122 "#54ffff",
123 "#ffffff",
124 #endif
128 /* more colors can be added after 255 to use with DefaultXX */
129 static const char *defextcolornames[] = {
130 "#cccccc", /* 256 */
131 "#333333", /* 257 */
132 /* root terminal fg and bg */
133 "#809a70", /* 258 */
134 "#002000", /* 259 */
135 /* bold and underline */
136 "#00afaf", /* 260 */
137 "#00af00", /* 261 */
141 /* Default colors (colorname index) foreground, background, cursor, unfocused cursor */
142 #define DEFAULT_FG (7)
143 #define DEFAULT_BG (0)
144 #define DEFAULT_CS (256)
145 #define DEFAULT_UCS (257)
147 #define TNAME "xterm"
149 /* double-click timeout (in milliseconds) between clicks for selection */
150 #define DOUBLECLICK_TIMEOUT (300)
151 #define TRIPLECLICK_TIMEOUT (2*DOUBLECLICK_TIMEOUT)
152 //#define SELECT_TIMEOUT 20 /* 20 ms */
153 #define DRAW_TIMEOUT 18 /* 18 ms */
155 #define TAB (8)
158 ////////////////////////////////////////////////////////////////////////////////
159 #define MAX_COLOR (511)
161 /* XEMBED messages */
162 #define XEMBED_FOCUS_IN (4)
163 #define XEMBED_FOCUS_OUT (5)
166 /* Arbitrary sizes */
167 #define ESC_TITLE_SIZ (256)
168 #define ESC_BUF_SIZ (256)
169 #define ESC_ARG_SIZ (16)
170 #define DRAW_BUF_SIZ (2048)
171 #define UTF_SIZ (4)
172 #define OBUFSIZ (256)
173 #define WBUFSIZ (256)
176 /* masks for key translation */
177 #define XK_NO_MOD (UINT_MAX)
178 #define XK_ANY_MOD (0)
181 /* misc utility macros */
182 //#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000+(t1.tv_usec-t2.tv_usec)/1000)
183 #define SERRNO strerror(errno)
184 #define MIN(a, b) ((a) < (b) ? (a) : (b))
185 #define MAX(a, b) ((a) < (b) ? (b) : (a))
186 #define LEN(a) (sizeof(a)/sizeof(a[0]))
187 #define DEFAULT(a, b) (a) = ((a) ? (a) : (b))
188 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
189 #define LIMIT(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x))
190 #define ATTRCMP(a, b) ((a).attr != (b).attr || (a).fg != (b).fg || (a).bg != (b).bg)
191 #define IS_SET(flag) (term->mode&(flag))
192 #define X2COL(x) ((x)/xw.cw)
193 #define Y2ROW(y) ((y-(opt_tabposition==1 ? xw.tabheight : 0))/xw.ch-(term!=NULL ? term->topline : 0))
194 #define IS_GFX(mode) ((mode)&ATTR_GFX)
197 ////////////////////////////////////////////////////////////////////////////////
198 enum {
199 BELL_AUDIO = 0x01,
200 BELL_URGENT = 0x02
203 enum {
204 CMDMODE_NONE,
205 CMDMODE_INPUT,
206 CMDMODE_MESSAGE
209 enum glyph_attribute {
210 ATTR_NULL = 0x00,
211 ATTR_REVERSE = 0x01,
212 ATTR_UNDERLINE = 0x02,
213 ATTR_BOLD = 0x04,
214 ATTR_GFX = 0x08,
215 ATTR_DEFFG = 0x10,
216 ATTR_DEFBG = 0x20,
219 enum cursor_movement {
220 CURSOR_UP,
221 CURSOR_DOWN,
222 CURSOR_LEFT,
223 CURSOR_RIGHT,
224 CURSOR_SAVE,
225 CURSOR_LOAD
228 enum cursor_state {
229 CURSOR_DEFAULT = 0x00,
230 CURSOR_HIDE = 0x01,
231 CURSOR_WRAPNEXT = 0x02
234 enum glyph_state {
235 GLYPH_SET = 0x01, /* for selection only */
236 GLYPH_DIRTY = 0x02,
237 GLYPH_WRAP = 0x10, /* can be set for the last line glyph */
241 enum term_mode {
242 MODE_WRAP = 0x01,
243 MODE_INSERT = 0x02,
244 MODE_APPKEYPAD = 0x04,
245 MODE_ALTSCREEN = 0x08,
246 MODE_CRLF = 0x10,
247 MODE_MOUSEBTN = 0x20,
248 MODE_MOUSEMOTION = 0x40,
249 MODE_MOUSE = 0x20|0x40,
250 MODE_REVERSE = 0x80,
251 MODE_BRACPASTE = 0x100,
252 MODE_FOCUSEVT = 0x200,
253 MODE_DISPCTRL = 0x400, //TODO: not implemented yet
254 MODE_GFX0 = 0x1000,
255 MODE_GFX1 = 0x2000,
258 enum escape_state {
259 ESC_START = 0x01,
260 ESC_CSI = 0x02,
261 ESC_OSC = 0x04,
262 ESC_TITLE = 0x08,
263 ESC_ALTCHARSET = 0x10,
264 ESC_HASH = 0x20,
265 ESC_PERCENT = 0x40,
266 ESC_ALTG1 = 0x80,
269 enum window_state {
270 WIN_VISIBLE = 0x01,
271 WIN_REDRAW = 0x02,
272 WIN_FOCUSED = 0x04,
275 /* bit macro */
276 //#undef B0
277 //enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 };
280 ////////////////////////////////////////////////////////////////////////////////
281 typedef unsigned char uchar;
282 typedef unsigned int uint;
283 typedef uint32_t uint32;
284 typedef uint16_t ushort;
287 typedef struct __attribute__((packed)) {
288 /* character code */
289 union __attribute__((packed)) {
290 uint32 uc;
291 char c[UTF_SIZ];
293 uchar attr; /* attribute flags */
294 ushort fg; /* foreground */
295 ushort bg; /* background */
296 uchar state; /* state flags */
297 } Glyph;
300 typedef Glyph *Line;
302 typedef struct {
303 Glyph attr; /* current char attributes */
304 int x;
305 int y;
306 char state;
307 } TCursor;
310 /* CSI Escape sequence structs */
311 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
312 typedef struct {
313 char buf[ESC_BUF_SIZ]; /* raw string */
314 int len; /* raw string length */
315 char priv;
316 int arg[ESC_ARG_SIZ];
317 int narg; /* nb of args */
318 char mode;
319 } CSIEscape;
322 /* Purely graphic info */
323 typedef struct {
324 Display *dpy;
325 Colormap cmap;
326 Window win;
327 Cursor cursor;
328 Cursor blankPtr;
329 Atom xembed;
330 XIM xim;
331 XIC xic;
332 int scr;
333 int w; /* window width */
334 int h; /* window height */
335 int bufw; /* pixmap width */
336 int bufh; /* pixmap height */
337 int ch; /* char height */
338 int cw; /* char width */
339 char state; /* focus, redraw, visible */
341 int tch; /* tab text char height */
342 Pixmap pictab;
343 int tabheight;
344 //struct timeval lastdraw;
345 } XWindow;
348 /* TODO: use better name for vars... */
349 typedef struct {
350 int mode;
351 int bx, by;
352 int ex, ey;
353 struct { int x, y; } b, e;
354 char *clip;
355 Atom xtarget;
356 int tclick1;
357 int tclick2;
358 } Selection;
361 /* Drawing Context */
362 typedef struct {
363 uint32 *ncol; // normal colors
364 uint32 *bcol; // b/w colors
365 uint32 *gcol; // green colors
366 uint32 *clrs[3];
367 GC gc;
368 struct {
369 int ascent;
370 int descent;
371 short lbearing;
372 short rbearing;
373 XFontSet set;
374 Font fid;
375 } font[3];
376 } DC;
379 typedef void (*CmdLineExecFn) (int cancelled);
382 #define CMDLINE_SIZE (256)
384 /* Internal representation of the screen */
385 typedef struct Term {
386 int cmdfd;
387 int dead;
388 int exitcode;
389 int needConv; /* 0: utf-8 locale */
390 int belltype;
391 int blackandwhite;
392 int fastredraw;
394 int curblink;
395 int curbhidden;
396 int lastBlinkTime;
397 int curblinkinactive;
399 int row; /* nb row */
400 int col; /* nb col */
401 int topline; /* top line for drawing (0: no history; 1: show one history line; etc) */
402 int linecount; /* full, with history */
403 int maxhistory;/* max history lines; 0: none; <0: infinite */
404 Line *line; /* screen */
405 Line *alt; /* alternate screen */
406 char *dirty; /* dirtyness of lines */
407 TCursor c; /* cursor */
408 int top; /* top scroll limit */
409 int bot; /* bottom scroll limit */
410 int mode; /* terminal mode flags */
411 int mousemode; /* mouse mode: 1000, 1005, 1006, 1015 */
412 int esc; /* escape state flags */
413 int charset; /* 0 or 1 */
415 TCursor csaved; /* saved cursor info */
416 // old cursor position
417 int oldcx;
418 int oldcy;
420 char title[ESC_TITLE_SIZ+1];
421 int titlelen;
423 int mouseob;
424 int mouseox;
425 int mouseoy;
427 char obuf[OBUFSIZ];
428 #ifdef DUMP_PROG_OUTPUT
429 int xobuflen;
430 #endif
431 int obuflen;
433 char ubuf[UTF_SIZ];
434 int ubufpos;
436 char drawbuf[DRAW_BUF_SIZ];
438 char wrbuf[WBUFSIZ];
439 int wrbufsize;
440 int wrbufused;
441 int wrbufpos;
443 CSIEscape escseq;
444 Selection sel;
445 pid_t pid;
446 int lastDrawTime;
448 char *execcmd;
450 Pixmap picbuf;
451 int picbufw;
452 int picbufh;
454 ushort deffg;
455 ushort defbg;
457 int wantRedraw;
459 int lastActiveTime;
461 int cmdMode;
462 char cmdline[UTF_SIZ*CMDLINE_SIZE];
463 int cmdreslen; // byte length of 'reserved' (read-only) part
464 int cmdofs;
465 char cmdc[UTF_SIZ+1];
466 int cmdcl;
467 int cmdtabpos;
468 const char *cmdprevc;
469 CmdLineExecFn cmdexecfn;
470 } Term;
473 ////////////////////////////////////////////////////////////////////////////////
474 /* Globals */
475 static ushort *unimap = NULL; // 0 or 65535 items; 127: special; high bit set: use alt(gfx) mode; 0: empty
477 static char **opt_cmd = NULL;
478 static char *opt_title = NULL;
479 static char *opt_embed = NULL;
480 static char *opt_class = NULL;
481 static char *opt_term = NULL;
482 static char *opt_fontnorm = NULL;
483 static char *opt_fontbold = NULL;
484 static char *opt_fonttab = NULL;
485 static char *opt_shell = NULL;
486 static char *opt_colornames[512];
487 static int opt_term_locked = 0;
488 static int opt_doubleclick_timeout = DOUBLECLICK_TIMEOUT;
489 static int opt_tripleclick_timeout = TRIPLECLICK_TIMEOUT;
490 static int opt_tabsize = TAB;
491 static int defaultFG = DEFAULT_FG;
492 static int defaultBG = DEFAULT_BG;
493 static int defaultCursorFG = 0;
494 static int defaultCursorBG = DEFAULT_CS;
495 static int defaultCursorInactiveFG = 0;
496 static int defaultCursorInactiveBG = DEFAULT_UCS;
497 static int defaultBoldFG = -1;
498 static int defaultUnderlineFG = -1;
499 static int normalTabFG = 258;
500 static int normalTabBG = 257;
501 static int activeTabFG = 258;
502 static int activeTabBG = 0;
503 static int opt_maxhistory = 512;
504 static int opt_ptrblank = 2000; // delay; 0: never
505 static int opt_tabcount = 6;
506 static int opt_tabposition = 0; // 0: bottom; 1: top
507 static int opt_drawtimeout = DRAW_TIMEOUT;
508 static int opt_disabletabs = 0;
509 static int opt_audiblebell = 1;
510 static int opt_urgentbell = 1;
511 static int opt_cursorBlink = 0;
512 static int opt_cursorBlinkInactive = 0;
513 static int opt_drawunderline = 1;
514 static int opt_ignoreclose = 0;
515 static int opt_maxdrawtimeout = 3000;
516 static int opt_fastredraw = 0;
517 static int ptrBlanked = 0;
518 static int ptrLastMove = 0;
519 static int globalBW = 0;
521 static Term **term_array = NULL;
522 static int term_count = 0;
523 static int term_array_size = 0;
524 static Term *term; // current terminal
525 static int termidx; // current terminal index; DON'T RELAY ON IT!
526 static int updateTabBar;
527 static int lastDrawTime = 0;
528 static char *lastSelStr = NULL;
529 //static int lastSelLength = 0;
531 static int firstVisibleTab = 0;
533 static int exitcode = 0;
534 static int closeRequestComes = 0;
536 static DC dc;
537 static XWindow xw;
539 static Atom XA_VT_SELECTION;
540 static Atom XA_CLIPBOARD;
541 static Atom XA_UTF8;
542 static Atom XA_TARGETS;
543 static Atom XA_NETWM_NAME;
544 static Atom XA_WM_DELETE_WINDOW;
547 ////////////////////////////////////////////////////////////////////////////////
548 typedef struct {
549 KeySym src;
550 KeySym dst;
551 } KeyTransDef;
554 static KeyTransDef *keytrans = NULL;
555 static int keytrans_size = 0;
556 static int keytrans_used = 0;
559 typedef struct {
560 KeySym key;
561 uint mask;
562 int kp;
563 char *str;
564 } KeyInfoDef;
567 static KeyInfoDef *keybinds = NULL;
568 static int keybinds_size = 0;
569 static int keybinds_used = 0;
571 static KeyInfoDef *keymap = NULL;
572 static int keymap_size = 0;
573 static int keymap_used = 0;
576 ////////////////////////////////////////////////////////////////////////////////
577 static void executeCommands (const char *str);
578 static const char *findCommandCompletion (const char *str, int slen, const char *prev);
580 static void ttyresize (void);
581 static void tputc (const char *c); // `c` is utf-8
582 static void ttywrite (const char *s, size_t n);
583 static void ttywritenoenc (const char *s, size_t n);
584 static void tsetdirt (int top, int bot);
585 static void tfulldirt (void);
587 static void xclearunused (void);
588 static void xseturgency (int add);
589 static void xfixsel (void);
590 static void xblankPointer (void);
591 static void xunblankPointer (void);
592 static void xdrawTabBar (void);
594 static void draw (int forced);
596 static void tcmdput (const char *s, int len);
599 static inline void ttywritestr (const char *s) { if (s != NULL && s[0]) ttywrite(s, strlen(s)); }
600 static inline void ttywritestrnoenc (const char *s) { if (s != NULL && s[0]) ttywritenoenc(s, strlen(s)); }
603 static inline uint32 getColor (int idx) {
604 if (globalBW && (term == NULL || !term->blackandwhite)) return dc.clrs[globalBW][idx];
605 if (term != NULL) return dc.clrs[term->blackandwhite%3][idx];
606 return dc.clrs[0][idx];
610 ////////////////////////////////////////////////////////////////////////////////
612 static void trimstr (char *s) {
613 char *e;
615 while (*s && isspace(*s)) ++s;
616 for (e = s+strlen(s); e > s; --e) if (!isspace(e[-1])) break;
617 if (e <= s) *s = 0; else *e = 0;
621 // parse the argument list
622 // return error message or NULL
623 // format:
624 // '*': skip
625 // 's': string (char *)
626 // 'i': integer (int *)
627 // 'b': boolean (int *)
628 // '|': optional arguments follows
629 // '.': stop parsing, ignore rest
630 // 'R': stop parsing, set rest ptr (char *)
631 // string modifiers (also for 'R'):
632 // '!' -- don't allow empty strings
633 // '-' -- trim spaces
634 // int modifiers:
635 // {lo,hi}
636 // {,hi}
637 // {lo}
638 // WARNING! `line` will be modified!
639 // WARNING! string pointers will point INSIDE `line`, so don't discard it!
640 // UGLY! REWRITE!
641 const char *iniParseArguments (char *line, const char *fmt, ...) {
642 va_list ap;
643 int inOptional = 0;
645 if (line == NULL) return "alas";
646 trimstr(line);
647 va_start(ap, fmt);
648 while (*fmt) {
649 char spec = *fmt++, *args;
651 if (spec == '|') { inOptional = 1; continue; }
652 if (spec == '.') { va_end(ap); return NULL; }
654 while (*line && isspace(*line)) ++line;
655 if (*line == '#') *line = 0;
657 if (spec == 'R') {
658 char **p = va_arg(ap, char **);
659 int noempty = 0;
661 while (*fmt) {
662 if (*fmt == '!') { ++fmt; noempty = 1; }
663 else if (*fmt == '-') { ++fmt; trimstr(line); }
664 else break;
666 va_end(ap);
667 if (noempty && !line[0]) return "invalid empty arg";
668 if (p != NULL) *p = line;
669 return NULL;
672 if (!line[0]) {
673 // end of line, stop right here
674 va_end(ap);
675 if (!inOptional) return "out of args";
676 return NULL;
679 args = line;
681 char *dest = args, qch = '#';
682 int n;
684 if (line[0] == '"' || line[0] == '\'') qch = *line++;
686 while (*line && *line != qch) {
687 if (qch == '#' && isspace(*line)) break;
689 if (*line == '\\') {
690 if (!line[1]) { va_end(ap); return "invalid escape"; }
691 switch (*(++line)) {
692 case 'n': *dest++ = '\n'; ++line; break;
693 case 'r': *dest++ = '\r'; ++line; break;
694 case 't': *dest++ = '\t'; ++line; break;
695 case 'a': *dest++ = '\a'; ++line; break;
696 case 'e': *dest++ = '\x1b'; ++line; break; // esc
697 case 's': *dest++ = ' '; ++line; break;
698 case 'x': // hex
699 ++line;
700 if (!isxdigit(*line)) { va_end(ap); return "invalid hex escape"; }
701 n = toupper(*line)-'0'; if (n > 9) n -= 7;
702 ++line;
703 if (isxdigit(*line)) {
704 int b = toupper(*line)-'0'; if (b > 9) b -= 7;
706 n = (n*16)+b;
707 ++line;
709 *dest++ = n;
710 break;
711 case '0': // octal
712 n = 0;
713 for (int f = 0; f < 4; ++f) {
714 if (*line < '0' || *line > '7') break;
715 n = (n*8)+(line[0]-'0');
716 if (n > 255) { va_end(ap); return "invalid oct escape"; }
717 ++line;
719 if (n == 0) { va_end(ap); return "invalid oct escape"; }
720 *dest++ = n;
721 break;
722 case '1'...'9': // decimal
723 n = 0;
724 for (int f = 0; f < 3; ++f) {
725 if (*line < '0' || *line > '9') break;
726 n = (n*8)+(line[0]-'0');
727 if (n > 255) { va_end(ap); return "invalid dec escape"; }
728 ++line;
730 if (n == 0) { va_end(ap); return "invalid oct escape"; }
731 *dest++ = n;
732 break;
733 default:
734 *dest++ = *line++;
735 break;
737 } else {
738 *dest++ = *line++;
741 if (qch != '#') {
742 if (*line != qch) return "unfinished string";
743 if (*line) ++line;
744 } else if (*line && *line != '#') ++line;
745 *dest = 0;
747 // now process and convert argument
748 switch (spec) {
749 case '*': /* skip */
750 break;
751 case 's': { /* string */
752 int noempty = 0, trim = 0;
753 char **p;
755 for (;;) {
756 if (*fmt == '!') { noempty = 1; ++fmt; }
757 else if (*fmt == '-') { trim = 1; ++fmt; }
758 else break;
761 if (trim) trimstr(args);
763 if (noempty && !args[0]) { va_end(ap); return "invalid empty string"; }
764 p = va_arg(ap, char **);
765 if (p != NULL) *p = args;
766 } break;
767 case 'i': /* int */
768 if (!args[0]) {
769 va_end(ap);
770 return "invalid integer";
771 } else {
772 int *p = va_arg(ap, int *);
773 long int n;
774 char *eptr;
776 trimstr(args);
777 n = strtol(args, &eptr, 0);
778 if (*eptr) { va_end(ap); return "invalid integer"; }
780 if (*fmt == '{') {
781 // check min/max
782 int minmax[2], haveminmax[2];
784 haveminmax[0] = haveminmax[1] = 0;
785 minmax[0] = minmax[1] = 0;
786 ++fmt;
787 for (int f = 0; f < 2; ++f) {
788 if (isdigit(*fmt) || *fmt == '-' || *fmt == '+') {
789 int neg = 0;
790 haveminmax[f] = 1;
791 if (*fmt == '-') neg = 1;
792 if (!isdigit(*fmt)) {
793 ++fmt;
794 if (!isdigit(*fmt)) { va_end(ap); return "invalid integer bounds"; }
796 while (isdigit(*fmt)) {
797 minmax[f] = minmax[f]*10+(fmt[0]-'0');
798 ++fmt;
800 if (neg) minmax[f] = -minmax[f];
801 //fprintf(stderr, "got: %d\n", minmax[f]);
803 if (*fmt == ',') {
804 if (f == 1) { va_end(ap); return "invalid integer bounds: extra comma"; }
805 // do nothing, we are happy
806 ++fmt;
807 } else if (*fmt == '}') {
808 // ok, done
809 break;
810 } else { va_end(ap); return "invalid integer bounds"; }
812 if (*fmt != '}') { va_end(ap); return "invalid integer bounds"; }
813 ++fmt;
815 //fprintf(stderr, "b: (%d,%d) (%d,%d)\n", haveminmax[0], minmax[0], haveminmax[1], minmax[1]);
816 if ((haveminmax[0] && n < minmax[0]) || (haveminmax[1] && n > minmax[1])) { va_end(ap); return "integer out of bounds"; }
819 if (p) *p = n;
821 break;
822 case 'b': { /* bool */
823 int *p = va_arg(ap, int *);
825 trimstr(args);
826 if (!args[0]) { va_end(ap); return "invalid boolean"; }
827 if (strcasecmp(args, "true") == 0 || strcasecmp(args, "on") == 0 ||
828 strcasecmp(args, "tan") == 0 || strcasecmp(args, "1") == 0 ||
829 strcasecmp(args, "yes") == 0) {
830 if (p) *p = 1;
831 } else if (strcasecmp(args, "false") == 0 || strcasecmp(args, "off") == 0 ||
832 strcasecmp(args, "ona") == 0 || strcasecmp(args, "0") == 0 ||
833 strcasecmp(args, "no") == 0) {
834 if (p) *p = 0;
835 } else {
836 va_end(ap);
837 return "invalid boolean";
839 } break;
840 default:
841 va_end(ap);
842 return "invalid format specifier";
845 va_end(ap);
846 while (*line && isspace(*line)) ++line;
847 if (!line[0] || line[0] == '#') return NULL;
848 return "extra args";
852 ////////////////////////////////////////////////////////////////////////////////
853 // UTF-8
855 static const unsigned char utf8Length[256] = {
856 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x00-0x0f
857 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x10-0x1f
858 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x20-0x2f
859 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x30-0x3f
860 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x40-0x4f
861 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x50-0x5f
862 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x60-0x6f
863 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x70-0x7f
864 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0x80-0x8f
865 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0x90-0x9f
866 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0xa0-0xaf
867 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0xb0-0xbf
868 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, //0xc0-0xcf c0-c1: overlong encoding: start of a 2-byte sequence, but code point <= 127
869 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, //0xd0-0xdf
870 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, //0xe0-0xef
871 4,4,4,4,4,8,8,8,8,8,8,8,8,8,8,8 //0xf0-0xff
875 // decode one utf-8 char from *buf to *u, return char length; return '?' on error
876 static int utf8decode (uint32 *u, const void *buf) {
877 const unsigned char *data = (const unsigned char *)buf;
878 unsigned char len = utf8Length[*data];
879 uint32 uc;
881 switch (len) {
882 case 0: // ascii
883 *u = *data;
884 return 1;
885 case 8: case 9: // invalid
886 *u = '?';
887 return 1;
889 // utf-8
890 uc = (*data++)&(0x7c>>len);
891 while (--len) {
892 if (utf8Length[*data] != 9) { uc = 0xffff; break; }
893 uc = (uc<<6)|((*data++)&0x3f);
895 if (uc > 0x10ffff) uc &= 0x1fffff;
896 if ((uc >= 0xd800 && uc <= 0xdfff) || // utf16/utf32 surrogates
897 (uc >= 0xfdd0 && uc <= 0xfdef) || // just for fun
898 (uc >= 0xfffe && uc <= 0xffff)) uc = '?'; // bad unicode
899 *u = uc;
900 return data-((const unsigned char *)buf);
904 // encode one utf-8 char from u to *buf, return char length
905 static int utf8encode (void *buf, uint32 uc) {
906 uchar *sp = (uchar *)buf;
907 int n;
909 if (uc < 0x80) { *sp = uc; return 1; } /* 0xxxxxxx */
910 uc &= 0x1fffff;
911 if (uc < 0x800) {
912 /* 110xxxxx */
913 *sp++ = (uc>>6)|0xc0;
914 n = 1;
915 } else if (uc < 0x10000) {
916 /* 1110xxxx */
917 *sp++ = (uc>>12)|0xe0;
918 n = 2;
919 } else if (uc <= 0x10FFFF) {
920 /* 11110xxx */
921 *sp++ = (uc>>18)|0xf0;
922 n = 3;
923 } else {
924 /* U+FFFD */
925 memcpy(sp, "\xEF\xBF\xBD", 3);
926 return 3;
928 for (int f = n; f > 0; --f) *sp++ = ((uc>>(6*(f-1)))&0x3f)|0x80; /* 10xxxxxx */
929 return n+1;
933 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
934 UTF-8 otherwise return 0 */
935 static int isfullutf8 (const void *buf, int buflen) {
936 if (buflen > 0) {
937 const unsigned char *data = (const unsigned char *)buf;
938 unsigned char len = utf8Length[*data++];
939 int res;
941 switch (len) {
942 case 0: case 8: case 9: return 1;
944 if ((res = (buflen >= len))) buflen = len;
945 for (int f = buflen-1; f > 0; --f) if (((*data++)&0xc0) != 0x80) return 1;
946 return res;
948 return 0;
952 static inline int utf8size (const void *buf) {
953 const unsigned char *data = (const unsigned char *)buf;
954 unsigned char len = utf8Length[*data];
956 switch (len) {
957 case 0: return 1;
958 case 8: case 9: return 0;
960 return len;
964 static int utf8strlen (const char *s) {
965 int len = 0;
967 while (*s) {
968 if (((unsigned char)(s[0])&0xc0) == 0xc0 || ((unsigned char)(s[0])&0x80) == 0) ++len;
969 ++s;
971 return len;
975 static void utf8choplast (char *s) {
976 int lastpos = 0;
978 for (char *t = s; *t; ++t) {
979 if (((unsigned char)(t[0])&0xc0) == 0xc0 || ((unsigned char)(t[0])&0x80) == 0) lastpos = (int)(t-s);
981 s[lastpos] = 0;
985 ////////////////////////////////////////////////////////////////////////////////
986 // utilities
987 static char *SPrintfVA (const char *fmt, va_list vaorig) {
988 char *buf = NULL;
989 int olen, len = 128;
991 buf = malloc(len);
992 if (buf == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
993 for (;;) {
994 char *nb;
995 va_list va;
997 va_copy(va, vaorig);
998 olen = vsnprintf(buf, len, fmt, va);
999 va_end(va);
1000 if (olen >= 0 && olen < len) return buf;
1001 if (olen < 0) olen = len*2-1;
1002 nb = realloc(buf, olen+1);
1003 if (nb == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
1004 buf = nb;
1005 len = olen+1;
1010 static __attribute__((format(printf,1,2))) char *SPrintf (const char *fmt, ...) {
1011 char *buf = NULL;
1012 va_list va;
1014 va_start(va, fmt);
1015 buf = SPrintfVA(fmt, va);
1016 va_end(va);
1017 return buf;
1021 static __attribute__((noreturn)) __attribute__((format(printf,1,2))) void die (const char *errstr, ...) {
1022 va_list ap;
1024 fprintf(stderr, "FATAL: ");
1025 va_start(ap, errstr);
1026 vfprintf(stderr, errstr, ap);
1027 va_end(ap);
1028 fprintf(stderr, "\n");
1029 exit(EXIT_FAILURE);
1033 ////////////////////////////////////////////////////////////////////////////////
1034 // getticks
1035 static struct timespec mclk_sttime; // starting time of monotonic clock
1038 static void mclock_init (void) {
1039 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &mclk_sttime);
1043 static int mclock_ticks (void) {
1044 struct timespec tp;
1046 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &tp);
1047 tp.tv_sec -= mclk_sttime.tv_sec;
1048 return tp.tv_sec*1000+(tp.tv_nsec/1000000);
1052 ////////////////////////////////////////////////////////////////////////////////
1053 // locale conversions
1054 static iconv_t icFromLoc;
1055 static iconv_t icToLoc;
1056 static int needConversion = 0;
1057 static const char *cliLocale = NULL;
1060 static void initLCConversion (void) {
1061 const char *lct = setlocale(LC_CTYPE, NULL);
1062 char *cstr;
1064 needConversion = 0;
1065 if (cliLocale == NULL) {
1066 if (strrchr(lct, '.') != NULL) lct = strrchr(lct, '.')+1;
1067 } else {
1068 lct = cliLocale;
1070 if (strcasecmp(lct, "utf8") == 0 || strcasecmp(lct, "utf-8") == 0) return;
1071 //fprintf(stderr, "locale: [%s]\n", lct);
1072 icFromLoc = iconv_open("UTF-8", lct);
1073 if (icFromLoc == (iconv_t)-1) die("can't initialize locale conversion");
1074 cstr = SPrintf("%s//TRANSLIT", lct);
1075 icToLoc = iconv_open(cstr, "UTF-8");
1076 free(cstr);
1077 if (icToLoc == (iconv_t)-1) die("can't initialize locale conversion");
1078 needConversion = 1;
1082 static int loc2utf (char *dest, const char *src, int len) {
1083 if (needConversion) {
1084 char *ibuf, *obuf;
1085 size_t il, ol, ool;
1087 ibuf = (char *)src;
1088 obuf = dest;
1089 il = len;
1090 ool = ol = il*4;
1091 il = iconv(icFromLoc, &ibuf, &il, &obuf, &ol);
1092 if (il == (size_t)-1) return 0;
1093 return ool-ol;
1094 } else {
1095 if (len > 0) memmove(dest, src, len);
1096 return len;
1101 static int utf2loc (char *dest, const char *src, int len) {
1102 if (needConversion) {
1103 char *ibuf, *obuf;
1104 size_t il, ol, ool;
1106 ibuf = (char *)src;
1107 obuf = dest;
1108 il = len;
1109 ool = ol = il*4;
1110 il = iconv(icToLoc, &ibuf, &il, &obuf, &ol);
1111 if (il == (size_t)-1) return 0;
1112 return ool-ol;
1113 } else {
1114 if (len > 0) memmove(dest, src, len);
1115 return len;
1120 ////////////////////////////////////////////////////////////////////////////////
1121 static void fixWindowTitle (const Term *t) {
1122 const char *title = (t != NULL) ? t->title : NULL;
1124 if (title == NULL || !title[0]) {
1125 title = opt_title;
1126 if (title == NULL) title = "";
1128 XStoreName(xw.dpy, xw.win, title);
1129 XChangeProperty(xw.dpy, xw.win, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, (const unsigned char *)title, strlen(title));
1133 // find latest active terminal (but not current %-)
1134 static int findTermToSwitch (void) {
1135 int maxlat = -1, idx = -1;
1137 for (int f = 0; f < term_count; ++f) {
1138 if (term != term_array[f] && term_array[f]->lastActiveTime > maxlat) {
1139 maxlat = term_array[f]->lastActiveTime;
1140 idx = f;
1143 if (idx < 0) {
1144 if (termidx == 0) idx = 0; else idx = termidx+1;
1145 if (idx > term_count) idx = term_count-1;
1147 return idx;
1151 static void fixFirstTab (void) {
1152 if (termidx < firstVisibleTab) firstVisibleTab = termidx;
1153 else if (termidx > firstVisibleTab+opt_tabcount-1) firstVisibleTab = termidx-opt_tabcount+1;
1154 if (firstVisibleTab < 0) firstVisibleTab = 0;
1155 updateTabBar = 1;
1159 static void switchToTerm (int idx, int redraw) {
1160 if (idx >= 0 && idx < term_count && term_array[idx] != NULL && term_array[idx] != term) {
1161 int tt = mclock_ticks();
1163 if (term != NULL) term->lastActiveTime = tt;
1164 termidx = idx;
1165 term = term_array[termidx];
1166 term->curbhidden = 0;
1167 term->lastBlinkTime = tt;
1169 fixFirstTab();
1171 xseturgency(0);
1172 tfulldirt();
1173 fixWindowTitle(term);
1174 updateTabBar = 1;
1175 XSetWindowBackground(xw.dpy, xw.win, getColor(term->defbg));
1176 xclearunused();
1177 if (redraw) draw(1);
1178 //FIXME: optimize memory allocations
1179 if (term->sel.clip != NULL && term->sel.bx >= 0) {
1180 if (lastSelStr != NULL) free(lastSelStr);
1181 lastSelStr = strdup(term->sel.clip);
1182 //fprintf(stderr, "newsel: [%s]\n", lastSelStr);
1184 xfixsel();
1185 //fprintf(stderr, "term #%d\n", termidx);
1186 //fprintf(stderr, "needConv: %d\n", term->needConv);
1191 static Term *termalloc (void) {
1192 Term *t;
1194 if (term_count >= term_array_size) {
1195 int newsz = (term_count==0) ? 1 : term_array_size+64;
1196 Term **n = realloc(term_array, sizeof(Term *)*newsz);
1198 if (n == NULL && term_count == 0) die("out of memory!");
1199 term_array = n;
1200 term_array_size = newsz;
1202 if ((t = calloc(1, sizeof(Term))) == NULL) return NULL;
1203 t->wrbufsize = WBUFSIZ;
1204 t->deffg = defaultFG;
1205 t->defbg = defaultBG;
1206 t->dead = 1;
1207 t->needConv = (needConversion ? 1 : 0);
1208 t->belltype = (opt_audiblebell?BELL_AUDIO:0)|(opt_urgentbell?BELL_URGENT:0);
1209 t->curblink = opt_cursorBlink;
1210 t->curblinkinactive = opt_cursorBlinkInactive;
1211 t->fastredraw = opt_fastredraw;
1212 term_array[term_count++] = t;
1213 return t;
1217 // newer delete last terminal!
1218 static void termfree (int idx) {
1219 if (idx >= 0 && idx < term_count && term_array[idx] != NULL) {
1220 Term *t = term_array[idx];
1222 if (t->pid != 0) {
1223 kill(t->pid, SIGKILL);
1224 return;
1226 if (t->cmdfd >= 0) {
1227 close(t->cmdfd);
1228 t->cmdfd = -1;
1230 exitcode = t->exitcode;
1231 if (idx == termidx) {
1232 if (term_count > 1) {
1233 t->dead = 1;
1234 //switchToTerm((idx > 0) ? idx-1 : 1, 0);
1235 switchToTerm(findTermToSwitch(), 0);
1236 return;
1238 term = NULL;
1240 for (int y = 0; y < t->row; ++y) free(t->alt[y]);
1241 for (int y = 0; y < t->linecount; ++y) {
1242 //fprintf(stderr, "y=%d\n", y);
1243 free(t->line[y]);
1245 free(t->dirty);
1246 free(t->alt);
1247 free(t->line);
1248 if (t->execcmd != NULL) free(t->execcmd);
1249 // condense array
1250 if (termidx > idx) {
1251 // not current, and current at the right
1252 --termidx;
1254 for (int f = idx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
1255 --term_count;
1256 XFreePixmap(xw.dpy, t->picbuf);
1257 free(t);
1262 static void termcleanup (void) {
1263 int f = 0, needredraw = 0;
1265 while (f < term_count) {
1266 if (term_array[f]->dead) {
1267 termfree(f);
1268 needredraw = 1;
1269 } else {
1270 ++f;
1273 if (needredraw) {
1274 updateTabBar = 1;
1275 draw(1);
1280 //FIXME: is it safe to assume that signal interrupted main program?
1281 static void sigchld (int a) {
1282 //if (waitpid(term->pid, &stat, 0) < 0) die("waiting for pid %hd failed: %s", term->pid, SERRNO);
1283 for (;;) {
1284 int stat = 0;
1285 pid_t res = waitpid(-1, &stat, WNOHANG);
1287 if (res == (pid_t)-1 || res == 0) break;
1289 for (int f = 0; f < term_count; ++f) {
1290 if (term_array[f]->pid == res) {
1291 // this terminal should die
1292 //if (term_count == 1) exit(0);
1293 //close(term_array[f]->cmdfd);
1294 //term_array[f]->cmdfd = -1;
1295 term_array[f]->dead = 1;
1296 term_array[f]->pid = 0;
1297 term_array[f]->exitcode = (WIFEXITED(stat)) ? WEXITSTATUS(stat) : 127;
1298 //fprintf(stderr, "exitcode=%d\n", term_array[f]->exitcode);
1299 updateTabBar = 1;
1300 break;
1304 signal(SIGCHLD, sigchld);
1308 ////////////////////////////////////////////////////////////////////////////////
1309 static void keytrans_reset (void) {
1310 if (keytrans) free(keytrans);
1311 keytrans = NULL;
1312 keytrans_size = 0;
1313 keytrans_used = 0;
1317 static void keytrans_add (const char *src, const char *dst) {
1318 KeySym kssrc = XStringToKeysym(src);
1319 KeySym ksdst = XStringToKeysym(dst);
1321 if (kssrc == NoSymbol) die("invalid keysym: '%s'", src);
1322 if (ksdst == NoSymbol) die("invalid keysym: '%s'", dst);
1323 if (kssrc == ksdst) return; // idiot
1325 for (int f = 0; f < keytrans_used; ++f) {
1326 if (keytrans[f].src == kssrc) {
1327 // replace
1328 keytrans[f].dst = ksdst;
1329 return;
1333 if (keytrans_used >= keytrans_size) {
1334 int newsize = keytrans_size+64;
1335 KeyTransDef *n = realloc(keytrans, sizeof(KeyTransDef)*newsize);
1337 if (n == NULL) die("out of memory");
1338 keytrans_size = newsize;
1339 keytrans = n;
1341 keytrans[keytrans_used].src = kssrc;
1342 keytrans[keytrans_used].dst = ksdst;
1343 ++keytrans_used;
1347 ////////////////////////////////////////////////////////////////////////////////
1348 static void parsekeyname (const char *str, KeySym *ks, uint *mask, int *kp) {
1349 char *s = alloca(strlen(str)+1);
1351 if (s == NULL) die("out of memory");
1352 strcpy(s, str);
1353 *kp = 0;
1354 *ks = NoSymbol;
1355 *mask = XK_NO_MOD;
1357 while (*s) {
1358 char *e, oc;
1359 uint mm = 0;
1360 int mod = 1;
1362 while (*s && isspace(*s)) ++s;
1363 for (e = s; *e && !isspace(*e) && *e != '+'; ++e) ;
1364 oc = *e; *e = 0;
1366 if (strcasecmp(s, "alt") == 0) mm = Mod1Mask;
1367 else if (strcasecmp(s, "win") == 0) mm = Mod4Mask;
1368 else if (strcasecmp(s, "ctrl") == 0) mm = ControlMask;
1369 else if (strcasecmp(s, "shift") == 0) mm = ShiftMask;
1370 else if (strcasecmp(s, "any") == 0) mm = XK_NO_MOD; //!
1371 else if (strcasecmp(s, "kpad") == 0) *kp = 1;
1372 else {
1373 mod = 0;
1374 if ((*ks = XStringToKeysym(s)) == NoSymbol) break;
1375 //fprintf(stderr, "[%s]\n", s);
1378 *e = oc;
1379 s = e;
1380 while (*s && isspace(*s)) ++s;
1381 if (mod) {
1382 if (*s != '+') { *ks = NoSymbol; break; }
1383 ++s;
1384 if (mm != 0) {
1385 if (mm == XK_NO_MOD) *mask = XK_ANY_MOD;
1386 else if (*mask == XK_NO_MOD) *mask = mm;
1387 else if (*mask != XK_ANY_MOD) *mask |= mm;
1389 } else {
1390 if (*s) { *ks = NoSymbol; break; }
1393 if (*ks == NoSymbol) die("invalid key name: '%s'", str);
1394 //fprintf(stderr, "mask=0x%08x, kp=%d\n", *mask, *kp);
1398 ////////////////////////////////////////////////////////////////////////////////
1399 static void keybinds_reset (void) {
1400 if (keybinds) free(keybinds);
1401 keybinds = NULL;
1402 keybinds_size = 0;
1403 keybinds_used = 0;
1407 static void keybind_add (const char *key, const char *act) {
1408 KeySym ks;
1409 uint mask;
1410 int kp;
1412 parsekeyname(key, &ks, &mask, &kp);
1414 for (int f = 0; f < keybinds_used; ++f) {
1415 if (keybinds[f].key == ks && keybinds[f].mask == mask) {
1416 // replace or remove
1417 free(keybinds[f].str);
1418 if (act == NULL || !act[0]) {
1419 // remove
1420 for (int c = f+1; c < keybinds_used; ++c) keybinds[c-1] = keybinds[c];
1421 } else {
1422 // replace
1423 if ((keybinds[f].str = strdup(act)) == NULL) die("out of memory");
1425 return;
1429 if (keybinds_used >= keybinds_size) {
1430 int newsize = keybinds_size+64;
1431 KeyInfoDef *n = realloc(keybinds, sizeof(KeyInfoDef)*newsize);
1433 if (n == NULL) die("out of memory");
1434 keybinds_size = newsize;
1435 keybinds = n;
1437 keybinds[keybinds_used].key = ks;
1438 keybinds[keybinds_used].mask = mask;
1439 keybinds[keybinds_used].kp = 0;
1440 if ((keybinds[keybinds_used].str = strdup(act)) == NULL) die("out of memory");
1441 ++keybinds_used;
1445 ////////////////////////////////////////////////////////////////////////////////
1446 static void keymap_reset (void) {
1447 if (keymap) free(keymap);
1448 keymap = NULL;
1449 keymap_size = 0;
1450 keymap_used = 0;
1454 static void keymap_add (const char *key, const char *act) {
1455 KeySym ks;
1456 uint mask;
1457 int kp;
1459 parsekeyname(key, &ks, &mask, &kp);
1461 for (int f = 0; f < keymap_used; ++f) {
1462 if (keymap[f].key == ks && keymap[f].mask == mask && keymap[f].kp == kp) {
1463 // replace or remove
1464 free(keymap[f].str);
1465 if (act == NULL) {
1466 // remove
1467 for (int c = f+1; c < keymap_used; ++c) keymap[c-1] = keymap[c];
1468 } else {
1469 // replace
1470 if ((keymap[f].str = strdup(act)) == NULL) die("out of memory");
1472 return;
1476 if (keymap_used >= keymap_size) {
1477 int newsize = keymap_size+128;
1478 KeyInfoDef *n = realloc(keymap, sizeof(KeyInfoDef)*newsize);
1480 if (n == NULL) die("out of memory");
1481 keymap_size = newsize;
1482 keymap = n;
1484 keymap[keymap_used].key = ks;
1485 keymap[keymap_used].mask = mask;
1486 keymap[keymap_used].kp = kp;
1487 if ((keymap[keymap_used].str = strdup(act)) == NULL) die("out of memory");
1488 ++keymap_used;
1492 ////////////////////////////////////////////////////////////////////////////////
1493 // selection
1494 static inline void setWantRedraw (void) {
1495 if (term != NULL) {
1496 term->wantRedraw = 1;
1497 if (!term->fastredraw) term->lastDrawTime = mclock_ticks(); //FIXME: avoid excess redraw?
1502 static void inline markDirty (int lineno, int flag) {
1503 if (term != NULL && lineno >= 0 && lineno < term->row) {
1504 term->dirty[lineno] |= flag;
1505 term->wantRedraw = 1;
1506 if (!term->fastredraw) term->lastDrawTime = mclock_ticks(); //FIXME: avoid excess redraw?
1511 static void selinit (void) {
1512 term->sel.tclick1 = term->sel.tclick2 = mclock_ticks();
1513 term->sel.mode = 0;
1514 term->sel.bx = -1;
1515 term->sel.clip = NULL;
1516 term->sel.xtarget = XA_UTF8;
1517 //if (term->sel.xtarget == None) term->sel.xtarget = XA_STRING;
1521 static void selhide (void) {
1522 if (term->sel.bx != -1) {
1523 term->sel.mode = 0;
1524 term->sel.bx = -1;
1525 tfulldirt();
1530 static inline int selected (int x, int y) {
1531 if (term->sel.bx == -1) return 0;
1533 if (y >= term->row) y = 0-(y-term->row+1);
1534 if (term->sel.ey == y && term->sel.by == y) {
1535 int bx = MIN(term->sel.bx, term->sel.ex);
1536 int ex = MAX(term->sel.bx, term->sel.ex);
1538 return BETWEEN(x, bx, ex);
1541 return
1542 ((term->sel.b.y < y && y < term->sel.e.y) || (y == term->sel.e.y && x <= term->sel.e.x)) ||
1543 (y == term->sel.b.y && x >= term->sel.b.x && (x <= term->sel.e.x || term->sel.b.y != term->sel.e.y));
1547 static void getbuttoninfo (XEvent *e, int *b, int *x, int *y) {
1548 if (b != NULL) *b = e->xbutton.button;
1549 if (x != NULL) *x = X2COL(e->xbutton.x);
1550 if (y != NULL) *y = Y2ROW(e->xbutton.y);
1551 term->sel.b.x = (term->sel.by < term->sel.ey ? term->sel.bx : term->sel.ex);
1552 term->sel.b.y = MIN(term->sel.by, term->sel.ey);
1553 term->sel.e.x = (term->sel.by < term->sel.ey ? term->sel.ex : term->sel.bx);
1554 term->sel.e.y = MAX(term->sel.by, term->sel.ey);
1558 static void mousereport (XEvent *e) {
1559 int x = X2COL(e->xbutton.x);
1560 int y = Y2ROW(e->xbutton.y);
1561 int button = e->xbutton.button;
1562 int state = e->xbutton.state;
1563 //char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
1564 char buf[32], *p;
1565 char lastCh = 'M';
1566 int ss;
1568 if (term == NULL) return;
1569 if (x < 0 || x >= term->col || y < 0 || y >= term->row) return;
1571 //fprintf(stderr, "mr(%d): x=%d; y=%d\n", term->mousemode, x, y);
1572 #if 0
1573 case 1000: /* X11 xterm mouse reporting */
1574 case 1005: /* utf-8 mouse encoding */
1575 case 1006: /* sgr mouse encoding */
1576 case 1015: /* urxvt mouse encoding */
1577 #endif
1578 //sprintf(buf, "\x1b[M%c%c%c", 0, 32+x+1, 32+y+1);
1579 p = buf+sprintf(buf, "\x1b[M");
1580 /* from urxvt */
1581 if (e->xbutton.type == MotionNotify) {
1582 if (!IS_SET(MODE_MOUSEMOTION) || (x == term->mouseox && y == term->mouseoy)) return;
1583 button = term->mouseob+32;
1584 term->mouseox = x;
1585 term->mouseoy = y;
1586 } else if (e->xbutton.type == ButtonRelease || button == AnyButton) {
1587 if (term->mousemode != 1006) {
1588 button = 3; // 'release' flag
1589 } else {
1590 lastCh = 'm';
1591 button -= Button1;
1592 if (button >= 3) button += 64-3;
1594 } else {
1595 button -= Button1;
1596 if (button >= 3) button += 64-3;
1597 if (e->xbutton.type == ButtonPress) {
1598 term->mouseob = button;
1599 term->mouseox = x;
1600 term->mouseoy = y;
1603 ss = (state & ShiftMask ? 4 : 0)+(state & Mod4Mask ? 8 : 0)+(state & ControlMask ? 16 : 0);
1604 switch (term->mousemode) {
1605 case 1006: /* sgr */
1606 buf[2] = '<';
1607 p += sprintf(p, "%d;", button+ss);
1608 break;
1609 case 1015: /* urxvt */
1610 --p; // remove 'M'
1611 p += sprintf(p, "%d;", 32+button+ss);
1612 break;
1613 default:
1614 *p++ = 32+button+ss;
1615 break;
1617 // coords
1618 switch (term->mousemode) {
1619 case 1005: /* utf-8 */
1620 p += utf8encode(p, x+1);
1621 p += utf8encode(p, y+1);
1622 break;
1623 case 1006: /* sgr */
1624 p += sprintf(p, "%d;%d%c", x+1, y+1, lastCh);
1625 break;
1626 case 1015: /* urxvt */
1627 p += sprintf(p, "%d;%dM", x+1, y+1);
1628 break;
1629 default:
1630 p += sprintf(p, "%c%c", 32+x+1, 32+y+1);
1631 break;
1633 *p = 0;
1636 fprintf(stderr, "(%d)<", term->mousemode);
1637 for (const unsigned char *s = (const unsigned char *)buf; *s; ++s) {
1638 if (s[0] < 32) fprintf(stderr, "{%d}", s[0]); else fputc(s[0], stderr);
1640 fputs(">\n", stderr);
1643 ttywritestrnoenc(buf);
1647 static void xfixsel (void) {
1648 if (lastSelStr != NULL) {
1649 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
1650 XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, xw.win, CurrentTime);
1651 } else {
1652 if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) == xw.win) XSetSelectionOwner(xw.dpy, XA_PRIMARY, None, CurrentTime);
1653 if (XGetSelectionOwner(xw.dpy, XA_CLIPBOARD) == xw.win) XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, None, CurrentTime);
1655 XFlush(xw.dpy);
1659 static void xsetsel (char *str) {
1660 /* register the selection for both the clipboard and the primary */
1661 if (term == NULL) return;
1662 if (term->sel.clip != NULL) free(term->sel.clip);
1663 term->sel.clip = str;
1664 if (lastSelStr != NULL) free(lastSelStr);
1665 lastSelStr = (str != NULL ? strdup(str) : NULL);
1666 xfixsel();
1667 //fprintf(stderr, "[%s]\n", str);
1671 static void selclear (XEvent *e) {
1672 if (lastSelStr != NULL) free(lastSelStr);
1673 lastSelStr = NULL;
1674 if (term != NULL) {
1675 if (term->sel.clip != NULL) free(term->sel.clip);
1676 term->sel.clip = NULL;
1677 term->sel.mode = 0;
1678 if (term->sel.bx != 0) {
1679 term->sel.bx = -1;
1680 tfulldirt();
1681 draw(1);
1687 static Line selgetlinebyy (int y) {
1688 Line l;
1690 if (y >= term->row) return NULL;
1691 if (y < 0) {
1692 if (y < -(term->maxhistory)) return NULL;
1693 l = term->line[term->row+(-y)-1];
1694 } else {
1695 l = term->line[y];
1697 return l;
1701 static void selcopy (void) {
1702 char *str, *ptr;
1703 int x, y, bufsize, is_selected = 0;
1705 if (term == NULL || term->sel.bx == -1) {
1706 str = NULL;
1707 } else {
1708 int sy = MIN(term->sel.e.y, term->sel.b.y);
1709 int ey = MAX(term->sel.e.y, term->sel.b.y);
1712 fprintf(stderr, "bx=%d; ex=%d; by=%d; ey=%d\n", term->sel.bx, term->sel.by, term->sel.ex, term->sel.ey);
1713 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);
1714 fprintf(stderr, " sy=%d; ey=%d\n", sy, ey);
1716 if (ey >= term->row) { selclear(NULL); return; }
1717 bufsize = (term->col+1)*(ey-sy+1)*UTF_SIZ;
1718 ptr = str = malloc(bufsize);
1719 /* append every set & selected glyph to the selection */
1720 for (y = sy; y <= ey; ++y) {
1721 Line l = selgetlinebyy(y);
1722 char *pstart = ptr;
1724 if (l == NULL) continue;
1725 for (x = 0; x < term->col; ++x) {
1726 if ((is_selected = selected(x, y)) != 0) {
1727 int size = utf8size(l[x].c);
1729 //if (size == 1) fprintf(stderr, "x=%d; y=%d; size=%d; c=%d\n", x, y, size, l[x].c[0]);
1730 if (size == 1) {
1731 unsigned char c = (unsigned char)l[x].c[0];
1733 *ptr = (c < 32 || c >= 127) ? ' ' : c;
1734 } else if (size > 0) {
1735 memcpy(ptr, l[x].c, size);
1737 ptr += size;
1740 //fprintf(stderr, "y=%d; linebytes=%d\n", y, (int)(ptr-pstart));
1741 // trim trailing spaces
1742 while (ptr > pstart && ptr[-1] == ' ') --ptr;
1743 // \n at the end of every unwrapped selected line except for the last one
1744 if (is_selected && y < ey && selected(term->col-1, y) && !(l[term->col-1].state&GLYPH_WRAP)) *ptr++ = '\n';
1746 *ptr = 0;
1748 xsetsel(str);
1749 if (!str || !str[0]) selclear(NULL);
1753 static void selnotify (XEvent *e) {
1754 unsigned long nitems, ofs, rem;
1755 int format;
1756 uchar *data;
1757 Atom type;
1758 XSelectionEvent *se = (XSelectionEvent *)e;
1759 int isutf8;
1760 int wasbrk = 0;
1761 char *ucbuf = NULL;
1762 int ucbufsize = 0;
1764 if (term == NULL || term->cmdMode == CMDMODE_MESSAGE) return;
1765 #ifdef PASTE_SELECTION_DEBUG
1767 char *name;
1769 fprintf(stderr, "selnotify!\n");
1771 name = se->selection != None ? XGetAtomName(se->display, se->selection) : NULL;
1772 fprintf(stderr, " selection: [%s]\n", name);
1773 if (name != NULL) XFree(name);
1775 name = se->target != None ? XGetAtomName(se->display, se->target) : NULL;
1776 fprintf(stderr, " target: [%s]\n", name);
1777 if (name != NULL) XFree(name);
1779 name = se->property != None ? XGetAtomName(se->display, se->property) : NULL;
1780 fprintf(stderr, " property: [%s]\n", name);
1781 if (name != NULL) XFree(name);
1783 #endif
1785 if (se->property != XA_VT_SELECTION) return;
1787 #ifdef PASTE_SELECTION_DEBUG
1789 fprintf(stderr, "selection:\n");
1790 fprintf(stderr, " primary: %d\n", se->selection == XA_PRIMARY);
1791 fprintf(stderr, " secondary: %d\n", se->selection == XA_SECONDARY);
1792 fprintf(stderr, " clipboard: %d\n", se->selection == XA_CLIPBOARD);
1793 fprintf(stderr, " vtsel: %d\n", se->selection == XA_VT_SELECTION);
1794 fprintf(stderr, "target:\n");
1795 fprintf(stderr, " primary: %d\n", se->target == XA_PRIMARY);
1796 fprintf(stderr, " secondary: %d\n", se->target == XA_SECONDARY);
1797 fprintf(stderr, " clipboard: %d\n", se->target == XA_CLIPBOARD);
1798 fprintf(stderr, " vtsel: %d\n", se->target == XA_VT_SELECTION);
1800 #endif
1801 if (se->target == XA_UTF8) {
1802 isutf8 = 1;
1803 } else if (se->target == XA_STRING) {
1804 isutf8 = 0;
1805 } else if (se->target == XA_TARGETS) {
1806 Atom rqtype = None, *targ;
1808 if (XGetWindowProperty(xw.dpy, xw.win, se->property, 0, 65536, False, XA_ATOM, &type, &format, &nitems, &rem, &data)) {
1809 //fprintf(stderr, "no targets\n");
1810 rqtype = XA_STRING;
1811 } else {
1812 for (targ = (Atom *)data; nitems > 0; --nitems, ++targ) {
1813 #ifdef PASTE_SELECTION_DEBUG
1814 fprintf(stderr, " TGT: [%s]\n", XGetAtomName(se->display, *targ));
1815 #endif
1816 if (*targ == XA_UTF8) rqtype = XA_UTF8;
1817 else if (*targ == XA_STRING && rqtype == None) rqtype = XA_STRING;
1819 XFree(data);
1821 if (rqtype != None) XConvertSelection(xw.dpy, se->selection, rqtype, XA_VT_SELECTION, xw.win, CurrentTime);
1822 return;
1823 } else {
1824 return;
1827 ofs = 0;
1828 do {
1829 int blen;
1830 char *str;
1832 if (XGetWindowProperty(xw.dpy, xw.win, se->property, ofs, BUFSIZ/4, False, AnyPropertyType, &type, &format, &nitems, &rem, &data)) {
1833 fprintf(stderr, "Clipboard allocation failed\n");
1834 break;
1836 //fprintf(stderr, "nitems=%d; format=%d; rem=%d\n", (int)nitems, format, (int)rem);
1837 blen = nitems*format/8;
1839 if (!isutf8) {
1840 int newsz = blen*4+64;
1842 if (ucbufsize < newsz) {
1843 char *n = realloc(ucbuf, newsz);
1845 if (n == NULL) { XFree(data); break; }
1846 ucbuf = n;
1847 ucbufsize = newsz;
1850 blen = loc2utf(ucbuf, (const char *)data, blen);
1851 str = ucbuf;
1852 } else {
1853 str = (char *)data;
1856 if (term->cmdMode != CMDMODE_NONE) {
1857 tcmdput(str, blen);
1858 } else {
1859 if (nitems*format/8 > 0 && !wasbrk && IS_SET(MODE_BRACPASTE)) {
1860 wasbrk = 1;
1861 ttywritestrnoenc("\x1b[200~");
1863 ttywrite(str, blen);
1865 XFree(data);
1866 /* number of 32-bit chunks returned */
1867 ofs += nitems*format/32;
1868 } while (rem > 0);
1870 if (wasbrk) ttywritestrnoenc("\x1b[201~");
1871 if (ucbuf != NULL) free(ucbuf);
1875 static void selpaste (Atom which) {
1876 if (term == NULL) return;
1877 if (XGetSelectionOwner(xw.dpy, which) == None) return;
1878 //XConvertSelection(xw.dpy, which, term->sel.xtarget, XA_VT_SELECTION, xw.win, CurrentTime);
1879 XConvertSelection(xw.dpy, which, XA_TARGETS, XA_VT_SELECTION, xw.win, CurrentTime);
1881 if (which == XA_PRIMARY) selpaste(XA_SECONDARY);
1882 else if (which == XA_SECONDARY) selpaste(XA_CLIPBOARD);
1887 static void selrequest (XEvent *e) {
1888 XSelectionRequestEvent *xsre;
1889 XSelectionEvent xev;
1891 if (lastSelStr == NULL) return;
1892 xsre = (XSelectionRequestEvent *)e;
1893 xev.type = SelectionNotify;
1894 xev.requestor = xsre->requestor;
1895 xev.selection = xsre->selection;
1896 xev.target = xsre->target;
1897 xev.time = xsre->time;
1898 /* reject */
1899 xev.property = None;
1900 if (xsre->target == XA_TARGETS) {
1901 /* respond with the supported type */
1902 Atom tlist[3] = {XA_UTF8, XA_STRING, XA_TARGETS};
1904 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_ATOM, 32, PropModeReplace, (uchar *)tlist, 3);
1905 xev.property = xsre->property;
1906 } else if (xsre->target == XA_UTF8 && lastSelStr != NULL) {
1907 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_UTF8, 8, PropModeReplace, (uchar *)lastSelStr, strlen(lastSelStr));
1908 xev.property = xsre->property;
1909 } else if (xsre->target == XA_STRING && lastSelStr != NULL) {
1910 char *s = malloc(strlen(lastSelStr)*4+8);
1912 if (s != NULL) {
1913 int len = utf2loc(s, lastSelStr, strlen(lastSelStr));
1915 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_STRING, 8, PropModeReplace, (uchar *)s, len);
1916 xev.property = xsre->property;
1917 free(s);
1920 /* all done, send a notification to the listener */
1921 if (!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *)&xev)) fprintf(stderr, "Error sending SelectionNotify event\n");
1925 static int x2tab (int x) {
1926 if (x >= 0 && x < xw.w && xw.tabheight > 0) {
1927 x /= (xw.w/opt_tabcount)+firstVisibleTab;
1928 return (x >= 0 && x < term_count) ? x : -1;
1930 return -1;
1934 static void msTabSwitch (XEvent *e) {
1935 int tabn = x2tab(e->xbutton.x)+firstVisibleTab;
1937 if (tabn >= 0 && tabn != termidx) switchToTerm(tabn, 1);
1941 static void msTabScrollLeft (void) {
1942 if (firstVisibleTab > 0) {
1943 --firstVisibleTab;
1944 updateTabBar = 1;
1945 xdrawTabBar();
1950 static void msTabScrollRight (void) {
1951 int newidx = firstVisibleTab+1;
1953 if (newidx > term_count-opt_tabcount) return;
1954 firstVisibleTab = newidx;
1955 updateTabBar = 1;
1956 xdrawTabBar();
1960 static void bpress (XEvent *e) {
1961 if (term == NULL) return;
1963 if (xw.tabheight > 0) {
1964 if ((opt_tabposition == 0 && e->xbutton.y >= xw.h-xw.tabheight) ||
1965 (opt_tabposition != 0 && e->xbutton.y < xw.tabheight)) {
1966 switch (e->xbutton.button) {
1967 case Button1: // left
1968 msTabSwitch(e);
1969 break;
1970 case Button4: // wheel up
1971 msTabScrollLeft();
1972 break;
1973 case Button5: // wheel down
1974 msTabScrollRight();
1975 break;
1977 return;
1981 if ((e->xbutton.state&ShiftMask) != 0) {
1982 if (e->xbutton.button == Button1) {
1983 if (term->sel.bx != -1) tsetdirt(term->sel.b.y, term->sel.e.y);
1984 term->sel.mode = 1;
1985 term->sel.b.y = term->sel.e.y = term->row+1;
1986 term->sel.ex = term->sel.bx = X2COL(e->xbutton.x);
1987 term->sel.ey = term->sel.by = Y2ROW(e->xbutton.y);
1988 //fprintf(stderr, "x=%d; y=%d\n", term->sel.bx, term->sel.by);
1989 draw(1);
1990 return;
1993 if (e->xbutton.button == Button3) {
1994 term->sel.bx = -1;
1995 selcopy();
1996 draw(1);
1999 return;
2001 if (IS_SET(MODE_MOUSE)) mousereport(e);
2005 static void brelease (XEvent *e) {
2006 if (term == NULL) return;
2008 switch (opt_tabposition) {
2009 case 0: // bottom
2010 if (e->xbutton.y >= xw.h-xw.tabheight) return;
2011 break;
2012 case 1: // top
2013 if (e->xbutton.y < xw.tabheight) return;
2014 break;
2017 if ((e->xbutton.state&ShiftMask) == 0 && !term->sel.mode) {
2018 if (IS_SET(MODE_MOUSE)) mousereport(e);
2019 return;
2022 if (e->xbutton.button == Button2) {
2023 selpaste(XA_PRIMARY);
2024 } else if (e->xbutton.button == Button1) {
2025 term->sel.mode = 0;
2026 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey); // this sets sel.b and sel.e
2028 if (term->sel.bx == term->sel.ex && term->sel.by == term->sel.ey) {
2029 // single line, single char selection
2030 int now;
2032 markDirty(term->sel.ey, 2);
2033 term->sel.bx = -1;
2034 now = mclock_ticks();
2035 if (now-term->sel.tclick2 <= opt_tripleclick_timeout) {
2036 /* triple click on the line */
2037 term->sel.b.x = term->sel.bx = 0;
2038 term->sel.e.x = term->sel.ex = term->col;
2039 term->sel.b.y = term->sel.e.y = term->sel.ey;
2040 } else if (now-term->sel.tclick1 <= opt_doubleclick_timeout) {
2041 /* double click to select word */
2042 Line l = selgetlinebyy(term->sel.ey);
2044 if (l != NULL) {
2045 //FIXME: write better word selection code
2046 term->sel.bx = term->sel.ex;
2047 if (IS_GFX(l[term->sel.bx].attr)) {
2048 while (term->sel.bx > 0 && IS_GFX(l[term->sel.bx-1].attr)) --term->sel.bx;
2049 term->sel.b.x = term->sel.bx;
2050 while (term->sel.ex < term->col-1 && IS_GFX(l[term->sel.ex+1].attr)) ++term->sel.ex;
2051 } else {
2052 while (term->sel.bx > 0 && !IS_GFX(l[term->sel.bx-1].attr) && l[term->sel.bx-1].c[0] != ' ') --term->sel.bx;
2053 term->sel.b.x = term->sel.bx;
2054 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;
2056 term->sel.e.x = term->sel.ex;
2057 term->sel.b.y = term->sel.e.y = term->sel.ey;
2061 selcopy();
2062 draw(1);
2063 } else {
2064 // multiline or multichar selection
2065 selcopy();
2068 term->sel.tclick2 = term->sel.tclick1;
2069 term->sel.tclick1 = mclock_ticks();
2070 //draw(1);
2074 static void bmotion (XEvent *e) {
2075 if (term == NULL) return;
2077 switch (opt_tabposition) {
2078 case 0: // bottom
2079 if (e->xbutton.y >= xw.h-xw.tabheight) return;
2080 break;
2081 case 1: // top
2082 if (e->xbutton.y < xw.tabheight) return;
2083 break;
2086 if (term->sel.mode) {
2087 int oldey = term->sel.ey, oldex = term->sel.ex;
2089 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey); // this sets sel.b and sel.e
2090 if (oldey != term->sel.ey || oldex != term->sel.ex) {
2091 int starty = MIN(oldey, term->sel.ey);
2092 int endy = MAX(oldey, term->sel.ey);
2094 tsetdirt(starty, endy);
2095 draw(1);
2097 return;
2099 //if (IS_SET(MODE_MOUSE) && e->xbutton.button != 0) mousereport(e);
2103 ////////////////////////////////////////////////////////////////////////////////
2104 // tty init
2106 static void dump (char c) {
2107 static int col;
2109 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
2110 if (++col % 10 == 0) fprintf(stderr, "\n");
2115 static __attribute__((noreturn)) void execsh (const char *str) {
2116 char **args;
2118 if (str == NULL) {
2119 char *envshell = getenv("SHELL");
2121 DEFAULT(envshell, opt_shell);
2122 setenv("TERM", opt_term, 1);
2123 args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL};
2124 } else {
2125 int argc = 0;
2127 args = calloc(32768, sizeof(char *));
2128 if (args == NULL) exit(EXIT_FAILURE);
2129 while (*str) {
2130 const char *b;
2132 while (*str && isspace(*str)) ++str;
2133 if (!str[0]) break;
2135 b = str;
2136 while (*str && !isspace(*str)) {
2137 if (*str++ == '\\') {
2138 if (*str) ++str;
2142 args[argc] = calloc(str-b+1, 1);
2143 memcpy(args[argc], b, str-b);
2146 FILE *fo = fopen("z.log", "a");
2147 fprintf(fo, "%d: [%s]\n", argc, args[argc]);
2148 fclose(fo);
2151 ++argc;
2153 if (argc < 1) exit(EXIT_FAILURE);
2155 execvp(args[0], args);
2156 exit(EXIT_FAILURE);
2160 static int ttynew (Term *term) {
2161 int m, s;
2162 struct winsize w = {term->row, term->col, 0, 0};
2163 static int signalset = 0;
2165 if (openpty(&m, &s, NULL, NULL, &w) < 0) die("openpty failed: %s", SERRNO);
2166 term->cmdfd = m;
2167 ttyresize();
2168 term->cmdfd = -1;
2169 switch (term->pid = fork()) {
2170 case -1: /* error */
2171 fprintf(stderr, "fork failed");
2172 return -1;
2173 case 0: /* child */
2174 setsid(); /* create a new process group */
2175 dup2(s, STDIN_FILENO);
2176 dup2(s, STDOUT_FILENO);
2177 dup2(s, STDERR_FILENO);
2178 if (ioctl(s, TIOCSCTTY, NULL) < 0) die("ioctl TIOCSCTTY failed: %s", SERRNO);
2179 close(s);
2180 close(m);
2181 execsh(term->execcmd);
2182 break;
2183 default: /* master */
2184 close(s);
2185 term->cmdfd = m;
2186 term->dead = 0;
2187 ttyresize();
2188 if (!signalset) { signalset = 1; signal(SIGCHLD, sigchld); }
2189 break;
2191 return 0;
2195 ////////////////////////////////////////////////////////////////////////////////
2196 // tty r/w
2197 static int ttycanread (void) {
2198 for (;;) {
2199 fd_set rfd;
2200 struct timeval timeout = {0};
2202 if (term->dead || term->cmdfd < 0) return 0;
2203 FD_ZERO(&rfd);
2204 FD_SET(term->cmdfd, &rfd);
2205 if (select(term->cmdfd+1, &rfd, NULL, NULL, &timeout) < 0) {
2206 if (errno == EINTR) continue;
2207 die("select failed: %s", SERRNO);
2209 if (FD_ISSET(term->cmdfd, &rfd)) return 1;
2210 break;
2212 return 0;
2216 static int ttycanwrite (void) {
2217 for (;;) {
2218 fd_set wfd;
2219 struct timeval timeout = {0};
2221 if (term->dead || term->cmdfd < 0) return 0;
2222 FD_ZERO(&wfd);
2223 FD_SET(term->cmdfd, &wfd);
2224 if (select(term->cmdfd+1, NULL, &wfd, NULL, &timeout) < 0) {
2225 if (errno == EINTR) continue;
2226 die("select failed: %s", SERRNO);
2228 if (FD_ISSET(term->cmdfd, &wfd)) return 1;
2229 break;
2231 return 0;
2235 #ifdef DUMP_IO
2236 static void wrstr (const char *s, int len) {
2237 if (s == NULL) return;
2238 while (len-- > 0) {
2239 unsigned char c = (unsigned char)(*s++);
2241 if (c < 32) fprintf(stderr, "{%u}", c); else fwrite(&c, 1, 1, stderr);
2244 #endif
2247 static void ttyread (void) {
2248 char *ptr;
2249 int left;
2251 /* append read bytes to unprocessed bytes */
2252 if (term == NULL || term->dead || term->cmdfd < 0) return;
2253 #ifdef DUMP_PROG_OUTPUT
2254 term->xobuflen = term->obuflen;
2255 #endif
2256 left = OBUFSIZ-term->obuflen;
2257 //dlogf("0: ttyread before: free=%d, used=%d", left, term->obuflen);
2258 while (left > 0 && ttycanread()) {
2259 int ret;
2261 //if ((ret = read(term->cmdfd, term->obuf+term->obuflen, 1)) < 0) die("Couldn't read from shell: %s", SERRNO);
2262 if ((ret = read(term->cmdfd, term->obuf+term->obuflen, left)) < 0) {
2263 //fprintf(stderr, "Warning: couldn't read from tty #%d: %s\n", termidx, SERRNO);
2264 break;
2266 term->obuflen += ret;
2267 left -= ret;
2269 //dlogf("1: ttyread after: free=%d, used=%d", left, term->obuflen);
2270 /* process every complete utf8 char */
2271 #ifdef DUMP_PROG_OUTPUT
2273 FILE *fo = fopen("zlogo.log", "ab");
2274 if (fo) {
2275 fwrite(term->obuf+term->xobuflen, term->obuflen-term->xobuflen, 1, fo);
2276 fclose(fo);
2279 #endif
2280 ptr = term->obuf;
2281 if (term->needConv) {
2282 // need conversion from locale to utf-8
2283 //fprintf(stderr, "buf: %d bytes\n", term->obuflen);
2284 while (term->obuflen > 0) {
2285 char obuf[UTF_SIZ+1];
2286 int len;
2288 len = loc2utf(obuf, ptr, 1);
2289 #ifdef DUMP_IO_READ
2291 fprintf(stderr, "rdc: [");
2292 wrstr(ptr, 1);
2293 fprintf(stderr, "] --> [");
2294 wrstr(obuf, len);
2295 fprintf(stderr, "]\n");
2296 fflush(stderr);
2298 #endif
2299 if (len > 0) {
2300 obuf[len] = 0;
2301 tputc(obuf);
2303 ++ptr;
2304 --term->obuflen;
2306 term->obuflen = 0;
2307 } else {
2308 // don't do any conversion
2309 while (term->obuflen >= UTF_SIZ || isfullutf8(ptr, term->obuflen)) {
2310 uint32 utf8c;
2311 char s[UTF_SIZ+1];
2312 int charsize = utf8decode(&utf8c, ptr);
2313 int len;
2315 len = utf8encode(s, utf8c);
2316 #ifdef DUMP_IO_READ
2318 fprintf(stderr, "rdx: [");
2319 wrstr(s, len);
2320 fprintf(stderr, "]\n");
2321 fflush(stderr);
2323 #endif
2324 if (len > 0) {
2325 s[len] = 0;
2326 tputc(s);
2328 ptr += charsize;
2329 term->obuflen -= charsize;
2331 //dlogf("2: ttyread afterproc: used=%d", term->obuflen);
2333 /* keep any uncomplete utf8 char for the next call */
2334 if (term->obuflen > 0) memmove(term->obuf, ptr, term->obuflen);
2338 static void ttyflushwrbuf (void) {
2339 if (term == NULL || term->dead || term->cmdfd < 0) return;
2340 if (term->wrbufpos >= term->wrbufused) {
2341 term->wrbufpos = term->wrbufused = 0;
2342 return;
2344 //dlogf("0: ttyflushwrbuf before: towrite=%d", term->wrbufused-term->wrbufpos);
2345 while (term->wrbufpos < term->wrbufused && ttycanwrite()) {
2346 int ret;
2348 if ((ret = write(term->cmdfd, term->wrbuf+term->wrbufpos, term->wrbufused-term->wrbufpos)) == -1) {
2349 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2351 term->wrbufpos += ret;
2353 if (term->wrbufpos > 0) {
2354 int left = term->wrbufused-term->wrbufpos;
2356 if (left < 1) {
2357 // write buffer is empty
2358 term->wrbufpos = term->wrbufused = 0;
2359 } else {
2360 memmove(term->wrbuf, term->wrbuf+term->wrbufpos, left);
2361 term->wrbufpos = 0;
2362 term->wrbufused = left;
2365 //dlogf("1: ttyflushwrbuf after: towrite=%d", term->wrbufused-term->wrbufpos);
2369 // convert char to locale and write it
2370 static void ttywriterawchar (const char *s, int len, int noenc) {
2371 char loc[16];
2372 int clen;
2374 if (s == NULL || len < 1) return;
2375 if (!noenc) {
2376 if (term->needConv) {
2377 if ((clen = utf2loc(loc, s, len)) < 1) return;
2378 } else {
2379 if ((clen = utf8size(s)) < 1) return;
2380 memmove(loc, s, clen);
2382 } else {
2383 memmove(loc, s, (clen = len));
2385 #ifdef DUMP_IO_WRITE
2387 fprintf(stderr, "wrc: [");
2388 wrstr(s, len);
2389 fprintf(stderr, "] --> [");
2390 wrstr(loc, clen);
2391 fprintf(stderr, "]\n");
2392 fflush(stderr);
2394 #endif
2396 while (term->wrbufused+clen >= term->wrbufsize) {
2397 //FIXME: make write buffer dynamic?
2398 // force write at least one char
2399 //dlogf("ttywrite: forced write");
2400 if (write(term->cmdfd, term->wrbuf+term->wrbufpos, 1) == -1) {
2401 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2402 } else {
2403 ++term->wrbufpos;
2405 ttyflushwrbuf(); // make room for char
2407 memcpy(term->wrbuf+term->wrbufused, loc, clen);
2408 term->wrbufused += clen;
2412 static void ttywritenoenc (const char *s, size_t n) {
2413 if (n > 0) {
2414 term->ubufpos = 0; // discard possible utf-8 char
2415 while (n-- > 0) ttywriterawchar(s++, 1, 1);
2417 ttyflushwrbuf();
2421 static void ttywrite (const char *s, size_t n) {
2422 if (term == NULL || term->dead || term->cmdfd < 0) return;
2423 #ifdef DUMP_PROG_INPUT
2424 if (s != NULL && n > 0) {
2425 FILE *fo = fopen("zlogw.log", "ab");
2426 if (fo) {
2427 fwrite(s, n, 1, fo);
2428 fclose(fo);
2431 #endif
2432 //ttyflushwrbuf();
2433 if (s != NULL && n > 0) {
2434 while (n > 0) {
2435 unsigned char c = (unsigned char)(s[0]);
2437 if (term->ubufpos > 0 && isfullutf8(term->ubuf, term->ubufpos)) {
2438 // have complete char
2439 ttywriterawchar(term->ubuf, term->ubufpos, 0);
2440 term->ubufpos = 0;
2441 continue;
2444 if (term->ubufpos == 0) {
2445 // new char
2446 if (c < 128) {
2447 ttywriterawchar(s, 1, 0);
2448 } else if ((c&0xc0) == 0xc0) {
2449 // new utf-8 char
2450 term->ubuf[term->ubufpos++] = *s;
2451 } else {
2452 // ignore unsynced utf-8
2454 ++s;
2455 --n;
2456 continue;
2458 // char continues
2459 if (c < 128 || term->ubufpos >= UTF_SIZ || (c&0xc0) == 0xc0) {
2460 // discard previous utf-8, it's bad
2461 term->ubufpos = 0;
2462 continue;
2464 // collect
2465 term->ubuf[term->ubufpos++] = *s;
2466 ++s;
2467 --n;
2468 if (isfullutf8(term->ubuf, term->ubufpos)) {
2469 // have complete char
2470 ttywriterawchar(term->ubuf, term->ubufpos, 0);
2471 term->ubufpos = 0;
2475 ttyflushwrbuf();
2479 ////////////////////////////////////////////////////////////////////////////////
2480 // tty resize ioctl
2481 static void ttyresize (void) {
2482 struct winsize w;
2484 if (term != NULL && term->cmdfd >= 0) {
2485 w.ws_row = term->row;
2486 w.ws_col = term->col;
2487 w.ws_xpixel = w.ws_ypixel = 0;
2488 if (ioctl(term->cmdfd, TIOCSWINSZ, &w) < 0) fprintf(stderr, "Warning: couldn't set window size: %s\n", SERRNO);
2489 setWantRedraw();
2494 ////////////////////////////////////////////////////////////////////////////////
2495 // tty utilities
2496 static void csidump (void) {
2497 printf("^[ ");
2498 for (int f = 1; f < term->escseq.len; ++f) {
2499 uint c = (term->escseq.buf[f]&0xff);
2501 if (isprint(c)) putchar(c);
2502 else if (c == '\n') printf("(\\n)");
2503 else if (c == '\r') printf("(\\r)");
2504 else if (c == 0x1b) printf("(\\e)");
2505 else printf("(%02x)", c);
2507 putchar('\n');
2511 static void tsetdirt (int top, int bot) {
2512 LIMIT(top, 0, term->row-1);
2513 LIMIT(bot, 0, term->row-1);
2514 for (int y = top; y <= bot; ++y) markDirty(y, 2);
2518 static void tfulldirt (void) {
2519 tsetdirt(0, term->row-1);
2523 static void tmoveto (int x, int y) {
2524 LIMIT(x, 0, term->col-1);
2525 LIMIT(y, 0, term->row-1);
2526 term->c.state &= ~CURSOR_WRAPNEXT;
2527 if (term->c.x != x || term->c.y != y) {
2528 term->c.x = x;
2529 term->c.y = y;
2530 setWantRedraw();
2535 static void tclearregion (int x1, int y1, int x2, int y2) {
2536 int temp;
2538 //fprintf(stderr, "tclearregion: (%d,%d)-(%d,%d)\n", x1, y1, x2, y2);
2539 if (x1 > x2) { temp = x1; x1 = x2; x2 = temp; }
2540 if (y1 > y2) { temp = y1; y1 = y2; y2 = temp; }
2541 LIMIT(x1, 0, term->col-1);
2542 LIMIT(x2, 0, term->col-1);
2543 LIMIT(y1, 0, term->row-1);
2544 LIMIT(y2, 0, term->row-1);
2545 //fprintf(stderr, " tclearregion: (%d,%d)-(%d,%d)\n", x1, y1, x2, y2);
2546 for (int y = y1; y <= y2; ++y) {
2547 Line l = term->line[y];
2549 markDirty(y, (x1 <= 0 && x2 >= term->col-1) ? 2 : 1);
2550 for (int x = x1; x <= x2; ++x) {
2551 l[x].fg = term->c.attr.fg;
2552 l[x].bg = term->c.attr.bg;
2553 l[x].state = GLYPH_DIRTY;
2554 l[x].attr = ATTR_NULL|(term->c.attr.attr&(ATTR_DEFFG|ATTR_DEFBG));
2555 l[x].c[0] = ' ';
2556 if (term->sel.bx != -1 && selected(x, y)) selhide();
2558 l[term->col-1].state &= ~GLYPH_WRAP;
2563 static void tcursor (int mode) {
2564 if (mode == CURSOR_SAVE) {
2565 term->csaved = term->c;
2566 } else if (mode == CURSOR_LOAD) {
2567 term->c = term->csaved;
2568 tmoveto(term->c.x, term->c.y);
2569 setWantRedraw();
2574 static void treset (void) {
2575 Glyph g;
2577 term->c = (TCursor){{
2578 .attr = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG,
2579 .fg = 0,
2580 .bg = 0
2581 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
2582 term->c.attr.fg = term->deffg;
2583 term->c.attr.bg = term->defbg;
2585 g.state = GLYPH_DIRTY;
2586 g.attr = term->c.attr.attr;
2587 g.fg = term->c.attr.fg;
2588 g.bg = term->c.attr.bg;
2589 g.c[0] = ' ';
2590 g.c[1] = 0;
2592 term->top = 0;
2593 term->bot = term->row-1;
2594 term->mode = MODE_WRAP/* | MODE_MOUSEBTN*/ | MODE_GFX1;
2595 term->mousemode = 1000;
2596 term->charset = MODE_GFX0;
2597 //tclearregion(0, 0, term->col-1, term->row-1);
2598 for (int y = 0; y < term->row; ++y) {
2599 markDirty(y, 2);
2600 for (int x = 0; x < term->col; ++x) term->alt[y][x] = term->line[y][x] = g;
2602 for (int y = term->row; y < term->linecount; ++y) {
2603 for (int x = 0; x < term->col; ++x) term->line[y][x] = g;
2605 tcursor(CURSOR_SAVE);
2606 term->topline = 0;
2607 tfulldirt();
2611 static int tinitialize (int col, int row) {
2612 //memset(term, 0, sizeof(Term));
2613 //term->needConv = needConversion ? 1 : 0;
2614 term->wrbufsize = WBUFSIZ;
2615 term->deffg = term->deffg;
2616 term->defbg = term->defbg;
2617 term->row = row;
2618 term->col = col;
2619 term->dirty = calloc(term->row, sizeof(*term->dirty));
2620 term->maxhistory = opt_maxhistory;
2621 term->linecount = term->maxhistory+term->row;
2622 term->line = calloc(term->linecount, sizeof(Line));
2623 term->alt = calloc(term->row, sizeof(Line));
2624 for (int y = 0; y < term->linecount; ++y) term->line[y] = calloc(term->col, sizeof(Glyph));
2625 for (int y = 0; y < term->row; ++y) term->alt[y] = calloc(term->col, sizeof(Glyph));
2626 /* setup screen */
2627 treset();
2628 return 1;
2632 static void tadjustmaxhistory (int maxh) {
2633 if (term != NULL) {
2634 LIMIT(maxh, 0, 65535);
2635 if (term->maxhistory < maxh) {
2636 Line *nl;
2637 int newlc = term->linecount+(maxh-term->maxhistory);
2639 // add history lines
2640 if ((nl = realloc(term->line, sizeof(Line)*newlc)) != NULL) {
2641 Glyph g;
2643 term->topline = 0;
2644 term->line = nl;
2645 g.state = GLYPH_DIRTY;
2646 g.attr = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG;
2647 g.fg = term->deffg;
2648 g.bg = term->defbg;
2649 g.c[0] = ' ';
2650 g.c[1] = 0;
2651 for (int y = term->linecount; y < newlc; ++y) {
2652 term->line[y] = calloc(term->col, sizeof(Glyph));
2653 for (int x = 0; x < term->col; ++x) term->line[y][x] = g;
2655 term->maxhistory = maxh;
2656 term->linecount = newlc;
2658 } else if (term->maxhistory > maxh) {
2659 Line *nl;
2661 term->topline = 0;
2662 // remove history lines
2663 while (term->linecount > term->row+maxh) free(term->line[--term->linecount]);
2664 if ((nl = realloc(term->line, sizeof(Line)*term->linecount)) != NULL) term->line = nl;
2665 term->maxhistory = maxh;
2671 static void tswapscreen (void) {
2672 selhide();
2673 for (int f = 0; f < term->row; ++f) {
2674 Line t = term->line[f];
2676 term->line[f] = term->alt[f];
2677 term->alt[f] = t;
2679 term->mode ^= MODE_ALTSCREEN;
2680 tfulldirt();
2684 //FIXME: works bad with history
2685 //FIXME: ugly code
2686 static void selscroll (int orig, int n, int tohistory) {
2687 int docopy = 0;
2689 if (term->sel.bx == -1) return;
2691 tfulldirt(); // just in case
2692 if (!tohistory) {
2693 if (BETWEEN(term->sel.by, orig, term->bot) || BETWEEN(term->sel.ey, orig, term->bot)) {
2694 if ((term->sel.by += n) > term->bot || (term->sel.ey += n) < term->top) {
2695 selclear(NULL);
2696 return;
2698 if (term->sel.by < term->top) {
2699 term->sel.by = term->top;
2700 term->sel.bx = 0;
2701 docopy = 1;
2703 if (term->sel.ey > term->bot) {
2704 term->sel.ey = term->bot;
2705 term->sel.ex = term->col;
2706 docopy = 1;
2708 term->sel.b.x = term->sel.bx;
2709 term->sel.b.y = term->sel.by;
2710 term->sel.e.x = term->sel.ex;
2711 term->sel.e.y = term->sel.ey;
2713 } else {
2714 // tohistory!=0; always scrolls full screen up (n == -1)
2715 //fprintf(stderr, "selscroll to history\n");
2716 term->sel.by += n;
2717 term->sel.ey += n;
2718 //fprintf(stderr, " by=%d; ey=%d; maxhistory=%d\n", term->sel.by, term->sel.ey, term->maxhistory);
2719 if (term->sel.ey < 0 && -(term->sel.ey) > term->maxhistory) {
2720 // out of screen completely
2721 selclear(NULL);
2722 return;
2724 if (term->sel.by < 0 && -(term->sel.by) > term->maxhistory) {
2725 term->sel.by = -term->maxhistory;
2726 term->sel.bx = 0;
2727 docopy = 1;
2729 term->sel.b.x = term->sel.bx;
2730 term->sel.b.y = term->sel.by;
2731 term->sel.e.x = term->sel.ex;
2732 term->sel.e.y = term->sel.ey;
2735 if (docopy) selcopy();
2739 static void tscrolldown (int orig, int n) {
2740 Line temp;
2742 LIMIT(n, 0, term->bot-orig+1);
2743 if (n < 1) return;
2744 selscroll(orig, n, 0);
2745 //fprintf(stderr, "tscrolldown(%d, %d)\n", orig, n);
2746 tclearregion(0, term->bot-n+1, term->col-1, term->bot);
2747 for (int f = term->bot; f >= orig+n; --f) {
2748 temp = term->line[f];
2749 term->line[f] = term->line[f-n];
2750 term->line[f-n] = temp;
2751 markDirty(f, 2);
2752 markDirty(f-n, 2);
2757 static void tscrollup (int orig, int n, int tohistory) {
2758 Line temp;
2760 if (term == NULL) return;
2761 LIMIT(n, 0, term->bot-orig+1);
2762 if (n < 1) return;
2763 //fprintf(stderr, "tscrollup(%d, %d)\n", orig, n);
2764 if (tohistory && !IS_SET(MODE_ALTSCREEN) && term->maxhistory > 0) {
2765 Line l = term->line[term->linecount-1];
2767 for (int f = term->linecount-1; f > term->row; --f) term->line[f] = term->line[f-1];
2768 term->line[term->row] = l;
2769 for (int x = 0; x < term->col; ++x) l[x] = term->line[0][x];
2770 } else {
2771 tohistory = 0;
2774 selscroll(orig, -n, tohistory);
2775 //tclearregion(0, orig, term->col-1, orig+n-1);
2776 for (int f = orig; f <= term->bot-n; ++f) {
2777 temp = term->line[f];
2778 term->line[f] = term->line[f+n];
2779 term->line[f+n] = temp;
2780 markDirty(f, 2);
2781 markDirty(f+n, 2);
2783 tclearregion(0, term->bot-n+1, term->col-1, term->bot);
2787 static inline void tsetcharwrap (int y, int wrap) {
2788 if (y >= 0 && y < term->row) {
2789 if (wrap) term->line[y][term->col-1].state |= GLYPH_WRAP;
2790 else term->line[y][term->col-1].state &= ~GLYPH_WRAP;
2795 static void tnewline (int first_col) {
2796 int y = term->c.y;
2798 tsetcharwrap(y, (first_col == 2)); // 2: wrapping
2799 if (y == term->bot) tscrollup(term->top, 1, 1); else ++y;
2800 tmoveto(first_col ? 0 : term->c.x, y);
2804 static void csiparse (void) {
2805 const char *p = term->escseq.buf;
2807 term->escseq.narg = 0;
2808 if (*p == '?') { term->escseq.priv = 1; ++p; }
2809 while (p < term->escseq.buf+term->escseq.len) {
2810 int n = term->escseq.arg[term->escseq.narg];
2812 for (; *p && isdigit(*p); ++p) n = n*10+(p[0]-'0');
2813 term->escseq.arg[term->escseq.narg] = n;
2815 if (*p == ';' && term->escseq.narg+1 < ESC_ARG_SIZ) {
2816 ++term->escseq.narg;
2817 ++p;
2818 } else {
2819 term->escseq.mode = *p;
2820 ++term->escseq.narg;
2821 break;
2827 static void tsetchar (const char *c) {
2828 char ub[UTF_SIZ];
2829 int rev = 0, gfx = 0;
2830 int x = term->c.x, y = term->c.y;
2832 if (x < 0 || x >= term->col || y < 0 || y >= term->row) return;
2834 if (!term->needConv && unimap != NULL && (unsigned char)c[0] >= 0x80) {
2835 uint32 cc;
2837 utf8decode(&cc, c);
2838 if (cc <= 65535) {
2839 ushort uc = unimap[cc];
2841 //fprintf(stderr, "unimap: %d [%02X] 0x%04x -> 0x%04x\n", (unsigned char)c[0], cc, uc);
2842 if (uc) {
2843 if (uc == 127) {
2844 // inversed space
2845 rev = 1;
2846 ub[0] = ' ';
2847 } else {
2848 if (uc&0x8000) {
2849 ub[0] = (uc&0x7f);
2850 gfx = 1;
2851 } else {
2852 utf8encode(ub, uc);
2855 c = ub;
2859 markDirty(y, 1);
2861 term->line[y][x] = term->c.attr;
2862 if (rev) term->line[y][x].attr ^= ATTR_REVERSE;
2863 if (gfx || (term->mode&term->charset)) {
2864 term->line[y][x].attr |= ATTR_GFX;
2865 } else {
2866 term->line[y][x].attr &= ~ATTR_GFX;
2869 term->line[y][x].state = (GLYPH_SET | GLYPH_DIRTY);
2870 memcpy(term->line[y][x].c, c, UTF_SIZ);
2872 if (IS_GFX(term->line[y][x].attr)) {
2873 unsigned char c = (unsigned char)(term->line[y][x].c[0]);
2875 if (c > 95 && c < 128) term->line[y][x].c[0] -= 95;
2876 else if (c > 127) term->line[y][x].c[0] = ' ';
2878 if (term->sel.bx != -1 && selected(x, y)) selhide();
2879 //dlogf("tsetchar(%d,%d): [%c] (%d); dirty=%d\n", x, y, c[0], term->wantRedraw, term->dirty[y]);
2883 static void tdeletechar (int n) {
2884 int src = term->c.x+n;
2885 int dst = term->c.x;
2886 int size = term->col-src;
2888 markDirty(term->c.y, 2);
2889 if (src >= term->col) {
2890 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2891 } else {
2892 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2893 tclearregion(term->col-n, term->c.y, term->col-1, term->c.y);
2898 static void tinsertblank (int n) {
2899 int src = term->c.x;
2900 int dst = src+n;
2901 int size = term->col-dst;
2903 markDirty(term->c.y, 2);
2904 if (dst >= term->col) {
2905 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2906 } else {
2907 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2908 tclearregion(src, term->c.y, dst-1, term->c.y);
2913 static void tinsertblankline (int n) {
2914 if (term->c.y < term->top || term->c.y > term->bot) return;
2915 tscrolldown(term->c.y, n);
2919 static void tdeleteline (int n) {
2920 if (term->c.y < term->top || term->c.y > term->bot) return;
2921 tscrollup(term->c.y, n, 0);
2925 static void tsetattr (int *attr, int l) {
2926 for (int f = 0; f < l; ++f) {
2927 switch (attr[f]) {
2928 case 0:
2929 term->c.attr.attr &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
2930 term->c.attr.attr |= ATTR_DEFFG | ATTR_DEFBG;
2931 term->c.attr.fg = term->deffg;
2932 term->c.attr.bg = term->defbg;
2933 break;
2934 case 1:
2935 term->c.attr.attr |= ATTR_BOLD;
2936 break;
2937 case 4:
2938 term->c.attr.attr |= ATTR_UNDERLINE;
2939 break;
2940 case 7:
2941 term->c.attr.attr |= ATTR_REVERSE;
2942 break;
2943 case 22:
2944 term->c.attr.attr &= ~ATTR_BOLD;
2945 break;
2946 case 24:
2947 term->c.attr.attr &= ~ATTR_UNDERLINE;
2948 break;
2949 case 27:
2950 term->c.attr.attr &= ~ATTR_REVERSE;
2951 break;
2952 case 38:
2953 if (f+2 < l && attr[f+1] == 5) {
2954 f += 2;
2955 if (BETWEEN(attr[f], 0, 255)) {
2956 term->c.attr.fg = attr[f];
2957 term->c.attr.attr &= ~ATTR_DEFFG;
2958 } else {
2959 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[f]);
2961 } else {
2962 //fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2963 term->c.attr.fg = term->deffg;
2964 term->c.attr.attr |= ATTR_DEFFG;
2966 break;
2967 case 39:
2968 term->c.attr.fg = term->deffg;
2969 term->c.attr.attr |= ATTR_DEFFG;
2970 break;
2971 case 48:
2972 if (f+2 < l && attr[f+1] == 5) {
2973 f += 2;
2974 if (BETWEEN(attr[f], 0, 255)) {
2975 term->c.attr.bg = attr[f];
2976 term->c.attr.attr &= ~ATTR_DEFBG;
2977 } else {
2978 fprintf(stderr, "erresc: bad bgcolor %d\n", attr[f]);
2980 } else {
2981 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2983 break;
2984 case 49:
2985 term->c.attr.bg = term->defbg;
2986 term->c.attr.attr |= ATTR_DEFBG;
2987 break;
2988 default:
2989 if (BETWEEN(attr[f], 30, 37)) { term->c.attr.fg = attr[f]-30; term->c.attr.attr &= ~ATTR_DEFFG; }
2990 else if (BETWEEN(attr[f], 40, 47)) { term->c.attr.bg = attr[f]-40; term->c.attr.attr &= ~ATTR_DEFBG; }
2991 else if (BETWEEN(attr[f], 90, 97)) { term->c.attr.fg = attr[f]-90+8; term->c.attr.attr &= ~ATTR_DEFFG; }
2992 else if (BETWEEN(attr[f], 100, 107)) { term->c.attr.bg = attr[f]-100+8; term->c.attr.attr &= ~ATTR_DEFBG; }
2993 else { fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]); csidump(); }
2994 break;
3000 static void tsetscroll (int t, int b) {
3001 int temp;
3003 LIMIT(t, 0, term->row-1);
3004 LIMIT(b, 0, term->row-1);
3005 if (t > b) {
3006 temp = t;
3007 t = b;
3008 b = temp;
3010 term->top = t;
3011 term->bot = b;
3015 ////////////////////////////////////////////////////////////////////////////////
3016 // esc processing
3017 static void csihandle (void) {
3018 switch (term->escseq.mode) {
3019 case '@': /* ICH -- Insert <n> blank char */
3020 DEFAULT(term->escseq.arg[0], 1);
3021 tinsertblank(term->escseq.arg[0]);
3022 break;
3023 case 'A': /* CUU -- Cursor <n> Up */
3024 case 'e':
3025 DEFAULT(term->escseq.arg[0], 1);
3026 tmoveto(term->c.x, term->c.y-term->escseq.arg[0]);
3027 break;
3028 case 'B': /* CUD -- Cursor <n> Down */
3029 DEFAULT(term->escseq.arg[0], 1);
3030 tmoveto(term->c.x, term->c.y+term->escseq.arg[0]);
3031 break;
3032 case 'C': /* CUF -- Cursor <n> Forward */
3033 case 'a':
3034 DEFAULT(term->escseq.arg[0], 1);
3035 tmoveto(term->c.x+term->escseq.arg[0], term->c.y);
3036 break;
3037 case 'D': /* CUB -- Cursor <n> Backward */
3038 DEFAULT(term->escseq.arg[0], 1);
3039 tmoveto(term->c.x-term->escseq.arg[0], term->c.y);
3040 break;
3041 case 'E': /* CNL -- Cursor <n> Down and first col */
3042 DEFAULT(term->escseq.arg[0], 1);
3043 tmoveto(0, term->c.y+term->escseq.arg[0]);
3044 break;
3045 case 'F': /* CPL -- Cursor <n> Up and first col */
3046 DEFAULT(term->escseq.arg[0], 1);
3047 tmoveto(0, term->c.y-term->escseq.arg[0]);
3048 break;
3049 case 'G': /* CHA -- Move to <col> */
3050 case '`': /* XXX: HPA -- same? */
3051 DEFAULT(term->escseq.arg[0], 1);
3052 tmoveto(term->escseq.arg[0]-1, term->c.y);
3053 break;
3054 case 'H': /* CUP -- Move to <row> <col> */
3055 case 'f': /* XXX: HVP -- same? */
3056 DEFAULT(term->escseq.arg[0], 1);
3057 DEFAULT(term->escseq.arg[1], 1);
3058 tmoveto(term->escseq.arg[1]-1, term->escseq.arg[0]-1);
3059 break;
3060 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
3061 case 'J': /* ED -- Clear screen */
3062 term->sel.bx = -1;
3063 switch (term->escseq.arg[0]) {
3064 case 0: /* below */
3065 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
3066 if (term->c.y < term->row-1) tclearregion(0, term->c.y+1, term->col-1, term->row-1);
3067 break;
3068 case 1: /* above */
3069 if (term->c.y > 1) tclearregion(0, 0, term->col-1, term->c.y-1);
3070 tclearregion(0, term->c.y, term->c.x, term->c.y);
3071 break;
3072 case 2: /* all */
3073 tclearregion(0, 0, term->col-1, term->row-1);
3074 break;
3075 default:
3076 goto unknown;
3078 break;
3079 case 'K': /* EL -- Clear line */
3080 switch (term->escseq.arg[0]) {
3081 case 0: /* right */
3082 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
3083 break;
3084 case 1: /* left */
3085 tclearregion(0, term->c.y, term->c.x, term->c.y);
3086 break;
3087 case 2: /* all */
3088 tclearregion(0, term->c.y, term->col-1, term->c.y);
3089 break;
3091 break;
3092 case 'S': /* SU -- Scroll <n> line up */
3093 DEFAULT(term->escseq.arg[0], 1);
3094 tscrollup(term->top, term->escseq.arg[0], 0);
3095 break;
3096 case 'T': /* SD -- Scroll <n> line down */
3097 DEFAULT(term->escseq.arg[0], 1);
3098 tscrolldown(term->top, term->escseq.arg[0]);
3099 break;
3100 case 'L': /* IL -- Insert <n> blank lines */
3101 DEFAULT(term->escseq.arg[0], 1);
3102 tinsertblankline(term->escseq.arg[0]);
3103 break;
3104 case 'l': /* RM -- Reset Mode */
3105 if (term->escseq.priv) {
3106 switch (term->escseq.arg[0]) {
3107 case 1: // 1001 for xterm compatibility
3108 DUMP_KEYPAD_SWITCH("1", "OFF");
3109 term->mode &= ~MODE_APPKEYPAD;
3110 break;
3111 case 5: /* DECSCNM -- Remove reverse video */
3112 if (IS_SET(MODE_REVERSE)) {
3113 term->mode &= ~MODE_REVERSE;
3114 tfulldirt();
3116 break;
3117 case 7: /* autowrap off */
3118 term->mode &= ~MODE_WRAP;
3119 break;
3120 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
3121 break;
3122 case 20: /* non-standard code? */
3123 term->mode &= ~MODE_CRLF;
3124 break;
3125 case 25: /* hide cursor */
3126 if ((term->c.state&CURSOR_HIDE) == 0) {
3127 term->c.state |= CURSOR_HIDE;
3128 term->wantRedraw = 1;
3130 break;
3131 case 1000: /* disable X11 xterm mouse reporting */
3132 term->mode &= ~MODE_MOUSEBTN;
3133 break;
3134 case 1002:
3135 term->mode &= ~MODE_MOUSEMOTION;
3136 break;
3137 case 1004:
3138 term->mode &= ~MODE_FOCUSEVT;
3139 break;
3140 case 1005: /* utf-8 mouse encoding */
3141 case 1006: /* sgr mouse encoding */
3142 case 1015: /* urxvt mouse encoding */
3143 term->mousemode = 1000;
3144 break;
3145 case 1049: /* = 1047 and 1048 */
3146 case 47:
3147 case 1047:
3148 if (IS_SET(MODE_ALTSCREEN)) {
3149 tclearregion(0, 0, term->col-1, term->row-1);
3150 tswapscreen();
3152 if (term->escseq.arg[0] != 1049) break;
3153 case 1048:
3154 tcursor(CURSOR_LOAD);
3155 break;
3156 case 2004: /* reset bracketed paste mode */
3157 term->mode &= ~MODE_BRACPASTE;
3158 break;
3159 default:
3160 goto unknown;
3162 } else {
3163 switch (term->escseq.arg[0]) {
3164 case 3:
3165 term->mode &= ~MODE_DISPCTRL;
3166 break;
3167 case 4:
3168 term->mode &= ~MODE_INSERT;
3169 break;
3170 default:
3171 goto unknown;
3174 break;
3175 case 'M': /* DL -- Delete <n> lines */
3176 DEFAULT(term->escseq.arg[0], 1);
3177 tdeleteline(term->escseq.arg[0]);
3178 break;
3179 case 'X': /* ECH -- Erase <n> char */
3180 DEFAULT(term->escseq.arg[0], 1);
3181 tclearregion(term->c.x, term->c.y, term->c.x + term->escseq.arg[0], term->c.y);
3182 break;
3183 case 'P': /* DCH -- Delete <n> char */
3184 DEFAULT(term->escseq.arg[0], 1);
3185 tdeletechar(term->escseq.arg[0]);
3186 break;
3187 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
3188 case 'd': /* VPA -- Move to <row> */
3189 DEFAULT(term->escseq.arg[0], 1);
3190 tmoveto(term->c.x, term->escseq.arg[0]-1);
3191 break;
3192 case 'h': /* SM -- Set terminal mode */
3193 if (term->escseq.priv) {
3194 switch (term->escseq.arg[0]) {
3195 case 1:
3196 DUMP_KEYPAD_SWITCH("1", "ON");
3197 term->mode |= MODE_APPKEYPAD;
3198 break;
3199 case 5: /* DECSCNM -- Reverve video */
3200 if (!IS_SET(MODE_REVERSE)) {
3201 term->mode |= MODE_REVERSE;
3202 tfulldirt();
3204 break;
3205 case 7:
3206 term->mode |= MODE_WRAP;
3207 break;
3208 case 20:
3209 term->mode |= MODE_CRLF;
3210 break;
3211 case 12: /* att610 -- Start blinking cursor (IGNORED) */
3212 /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
3213 if (term->escseq.narg > 1 && term->escseq.arg[1] != 25) break;
3214 case 25:
3215 if ((term->c.state&CURSOR_HIDE) != 0) {
3216 term->c.state &= ~CURSOR_HIDE;
3217 term->wantRedraw = 1;
3219 break;
3220 case 1000: /* 1000,1002: enable xterm mouse report */
3221 term->mode |= MODE_MOUSEBTN;
3222 break;
3223 case 1002:
3224 term->mode |= MODE_MOUSEMOTION;
3225 break;
3226 case 1004:
3227 term->mode |= MODE_FOCUSEVT;
3228 break;
3229 case 1005: /* utf-8 mouse encoding */
3230 case 1006: /* sgr mouse encoding */
3231 case 1015: /* urxvt mouse encoding */
3232 term->mousemode = term->escseq.arg[0];
3233 break;
3234 case 1049: /* = 1047 and 1048 */
3235 case 47:
3236 case 1047:
3237 if (IS_SET(MODE_ALTSCREEN)) tclearregion(0, 0, term->col-1, term->row-1); else tswapscreen();
3238 if (term->escseq.arg[0] != 1049) break;
3239 case 1048:
3240 tcursor(CURSOR_SAVE);
3241 break;
3242 case 2004: /* set bracketed paste mode */
3243 term->mode |= MODE_BRACPASTE;
3244 break;
3245 default: goto unknown;
3247 } else {
3248 switch (term->escseq.arg[0]) {
3249 case 3:
3250 term->mode |= MODE_DISPCTRL;
3251 break;
3252 case 4:
3253 term->mode |= MODE_INSERT;
3254 break;
3255 default:
3256 goto unknown;
3259 break;
3260 case 'm': /* SGR -- Terminal attribute (color) */
3261 tsetattr(term->escseq.arg, term->escseq.narg);
3262 break;
3263 case 'n':
3264 if (!term->escseq.priv) {
3265 switch (term->escseq.arg[0]) {
3266 case 5: /* Device status report (DSR) */
3267 ttywritestr("\x1b[0n");
3268 break;
3269 case 6: { /* cursor position report */
3270 char buf[32];
3272 sprintf(buf, "\x1b[%d;%dR", term->c.x+1, term->c.y+1);
3273 ttywritestr(buf);
3274 } break;
3277 break;
3278 case 'r': /* DECSTBM -- Set Scrolling Region */
3279 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
3280 // xterm compatibility
3281 DUMP_KEYPAD_SWITCH("1001", "OFF");
3282 term->mode &= ~MODE_APPKEYPAD;
3283 } else if (term->escseq.priv) {
3284 goto unknown;
3285 } else {
3286 DEFAULT(term->escseq.arg[0], 1);
3287 DEFAULT(term->escseq.arg[1], term->row);
3288 tsetscroll(term->escseq.arg[0]-1, term->escseq.arg[1]-1);
3289 tmoveto(0, 0);
3291 break;
3292 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
3293 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
3294 // xterm compatibility
3295 DUMP_KEYPAD_SWITCH("1001", "ON");
3296 term->mode |= MODE_APPKEYPAD;
3297 } else {
3298 tcursor(CURSOR_SAVE);
3300 break;
3301 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
3302 tcursor(CURSOR_LOAD);
3303 break;
3304 default:
3305 unknown:
3306 fprintf(stderr, "erresc: unknown csi ");
3307 csidump();
3308 break;
3313 static void csireset (void) {
3314 memset(&term->escseq, 0, sizeof(term->escseq));
3318 static void tputtab (void) {
3319 int space = opt_tabsize-term->c.x%opt_tabsize;
3321 if (space > 0) tmoveto(term->c.x+space, term->c.y);
3325 ////////////////////////////////////////////////////////////////////////////////
3326 // put char to output buffer or process command
3328 // return 1 if this was control character
3329 // return -1 if this should break esape sequence
3330 static int tputc_ctrl (char ascii) {
3331 int res = 1;
3333 if (term->esc&ESC_TITLE) return 0;
3335 switch (ascii) {
3336 case '\t': tputtab(); break;
3337 case '\b': tmoveto(term->c.x-1, term->c.y); break;
3338 case '\r': tmoveto(0, term->c.y); break;
3339 case '\f': case '\n': case '\v': tnewline(IS_SET(MODE_CRLF)?1:0); break; /* go to first col if the mode is set */
3340 case '\a':
3341 if (!(xw.state & WIN_FOCUSED) && (term->belltype&BELL_URGENT)) xseturgency(1);
3342 if (term->belltype&BELL_AUDIO) XBell(xw.dpy, 100);
3343 break;
3344 case 14: term->charset = MODE_GFX1; break;
3345 case 15: term->charset = MODE_GFX0; break;
3346 case 0x18: case 0x1a: res = -1; break; // do nothing, interrupt current escape sequence
3347 case 127: break; // ignore it
3348 case '\033': csireset(); term->esc = ESC_START; break;
3349 //case 0x9b: csireset(); term->esc = ESC_START | ESC_CSI; break;
3350 default: res = 0; break;
3352 return res;
3356 static void tputc (const char *c) {
3357 char ascii = *c;
3358 int ctl = tputc_ctrl(ascii);
3360 if (ctl > 0) return; // control char; should not break escape sequence
3361 if (ctl < 0) {
3362 // control char; should break escape sequence
3363 term->esc = 0;
3364 return;
3366 //dlogf("tputc: [%c]\n", c[0]);
3367 if (term->esc & ESC_START) {
3368 if (term->esc & ESC_CSI) {
3369 term->escseq.buf[term->escseq.len++] = ascii;
3370 if (BETWEEN(ascii, 0x40, 0x7E) || term->escseq.len >= ESC_BUF_SIZ) {
3371 term->esc = 0;
3372 csiparse();
3373 csihandle();
3375 } else if (term->esc & ESC_OSC) {
3376 /* TODO: handle other OSC */
3377 if (ascii == ';') {
3378 term->title[0] = 0;
3379 term->titlelen = 0;
3380 term->esc = ESC_START | ESC_TITLE;
3381 //updateTabBar = 1;
3383 } else if (term->esc & ESC_TITLE) {
3384 int len = utf8size(c);
3386 if (ascii == '\a' || term->titlelen+len >= ESC_TITLE_SIZ) {
3387 term->esc = 0;
3388 term->title[term->titlelen] = '\0';
3389 fixWindowTitle(term);
3390 updateTabBar = 1;
3391 } else if (len > 0) {
3392 memcpy(term->title+term->titlelen, c, len);
3393 term->titlelen += len;
3394 term->title[term->titlelen] = '\0';
3396 } else if (term->esc & ESC_ALTCHARSET) {
3397 term->esc = 0;
3398 switch (ascii) {
3399 case '0': /* Line drawing crap */
3400 term->mode |= MODE_GFX0;
3401 break;
3402 case 'B': /* Back to regular text */
3403 term->mode &= ~MODE_GFX0;
3404 break;
3405 default:
3406 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
3407 term->mode &= ~MODE_GFX0;
3408 break;
3410 } else if (term->esc & ESC_ALTG1) {
3411 term->esc = 0;
3412 switch (ascii) {
3413 case '0': /* Line drawing crap */
3414 term->mode |= MODE_GFX1;
3415 break;
3416 case 'B': /* Back to regular text */
3417 term->mode &= ~MODE_GFX1;
3418 break;
3419 default:
3420 fprintf(stderr, "esc unhandled charset: ESC ) %c\n", ascii);
3421 term->mode &= ~MODE_GFX1;
3422 break;
3424 } else if (term->esc & ESC_HASH) {
3425 term->esc = 0;
3426 switch (ascii) {
3427 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
3428 //tfillscreenwithE();
3429 break;
3431 } else if (term->esc & ESC_PERCENT) {
3432 term->esc = 0;
3433 switch (ascii) {
3434 case 'G': case '8':
3435 term->needConv = 0;
3436 break;
3437 default:
3438 term->needConv = (needConversion ? 1 : 0);
3439 break;
3441 } else {
3442 switch (ascii) {
3443 case '[': term->esc |= ESC_CSI; break;
3444 case ']': term->esc |= ESC_OSC; break;
3445 case '(': term->esc |= ESC_ALTCHARSET; break;
3446 case ')': term->esc |= ESC_ALTG1; break;
3447 case '#': term->esc |= ESC_HASH; break;
3448 case '%': term->esc |= ESC_PERCENT; break;
3449 case 'D': /* IND -- Linefeed */
3450 term->esc = 0;
3451 if (term->c.y == term->bot) tscrollup(term->top, 1, 1); else tmoveto(term->c.x, term->c.y+1);
3452 break;
3453 case 'E': /* NEL -- Next line */
3454 term->esc = 0;
3455 tnewline(1); /* always go to first col */
3456 break;
3457 case 'M': /* RI -- Reverse linefeed */
3458 term->esc = 0;
3459 if (term->c.y == term->top) tscrolldown(term->top, 1); else tmoveto(term->c.x, term->c.y-1);
3460 break;
3461 case 'c': /* RIS -- Reset to inital state */
3462 term->esc = 0;
3463 treset();
3464 break;
3465 case '=': /* DECPAM -- Application keypad */
3466 DUMP_KEYPAD_SWITCH("=", "ON");
3467 term->esc = 0;
3468 term->mode |= MODE_APPKEYPAD;
3469 break;
3470 case '>': /* DECPNM -- Normal keypad */
3471 DUMP_KEYPAD_SWITCH(">", "OFF");
3472 term->esc = 0;
3473 term->mode &= ~MODE_APPKEYPAD;
3474 break;
3475 case '7': /* DECSC -- Save Cursor */
3476 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
3477 //TODO?
3478 term->esc = 0;
3479 tcursor(CURSOR_SAVE);
3480 break;
3481 case '8': /* DECRC -- Restore Cursor */
3482 //TODO?
3483 term->esc = 0;
3484 tcursor(CURSOR_LOAD);
3485 break;
3486 case 'F': /* Cursor to lower left corner of screen */
3487 tmoveto(0, term->row-1);
3488 break;
3489 case 'Z': /* DEC private identification */
3490 term->esc = 0;
3491 ttywritestr("\x1b[?1;2c");
3492 break;
3493 default:
3494 term->esc = 0;
3495 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X ('%c')\n", (uchar)ascii, isprint(ascii)?ascii:'.');
3496 break;
3499 } else {
3500 //if (term->sel.bx != -1 && BETWEEN(term->c.y, term->sel.by, term->sel.ey)) term->sel.bx = -1;
3501 do {
3502 if (term->needConv && IS_GFX(term->c.attr.attr)) {
3503 uint32 cc;
3505 utf8decode(&cc, c);
3506 if (cc < 32 || cc >= 127) break; //FIXME: nothing at all?
3507 } else {
3508 if ((unsigned char)ascii < 32 || ascii == 127) break; // seems that this chars are empty too
3510 if (term->c.state&CURSOR_WRAPNEXT) {
3511 if (IS_SET(MODE_WRAP)) {
3512 // always go to first col
3513 tnewline(2);
3514 } else {
3515 tsetcharwrap(term->c.y, 0);
3516 break; // wrap is off, don't want more chars
3519 tsetchar(c);
3520 if (term->c.x+1 < term->col) tmoveto(term->c.x+1, term->c.y); else term->c.state |= CURSOR_WRAPNEXT;
3521 } while (0);
3526 static void tunshowhistory (void) {
3527 if (term != NULL && term->topline != 0) {
3528 term->topline = 0;
3529 term->wantRedraw = 1;
3530 term->lastDrawTime = 0;
3535 static void tsendfocusevent (int focused) {
3536 if (term != NULL && IS_SET(MODE_FOCUSEVT)) {
3537 ttywritestr("\x1b[");
3538 ttywrite(focused?"I":"O", 1);
3543 static void tcmdlinedirty (void) {
3544 if (term != NULL) {
3545 markDirty(term->row-term->topline-1, 2);
3546 term->wantRedraw = 1;
3551 static void tcmdlinefixofs (void) {
3552 int ofs, len;
3554 len = utf8strlen(term->cmdline);
3555 ofs = len-(term->col-1);
3556 if (ofs < 0) ofs = 0;
3557 for (term->cmdofs = 0; ofs > 0; --ofs) term->cmdofs += utf8size(term->cmdline+term->cmdofs);
3558 tcmdlinedirty();
3562 static void tcmdlinehide (void) {
3563 term->cmdMode = CMDMODE_NONE;
3564 term->cmdprevc = NULL;
3565 tcmdlinedirty();
3569 // utf-8
3570 static void tcmdlinemsg (const char *msg) {
3571 if (msg != NULL) {
3572 int ofs = 0;
3574 term->cmdMode = CMDMODE_MESSAGE;
3575 term->cmdofs = 0;
3576 term->cmdtabpos = -1;
3577 term->cmdprevc = NULL;
3579 while (*msg) {
3580 int len = utf8size(msg);
3582 if (len < 1 || ofs+len >= sizeof(term->cmdline)-1) break;
3583 memcpy(term->cmdline+ofs, msg, len);
3584 ofs += len;
3585 msg += len;
3588 term->cmdline[ofs] = 0;
3589 tcmdlinedirty();
3594 static __attribute__((format(printf,1, 2))) void tcmdlinemsgf (const char *fmt, ...) {
3595 char buf[128];
3596 char *xbuf = buf;
3597 int size = sizeof(buf)-1;
3598 va_list ap;
3600 for (;;) {
3601 int n;
3602 char *t;
3604 va_start(ap, fmt);
3605 n = vsnprintf(xbuf, size, fmt, ap);
3606 va_end(ap);
3607 if (n > -1 && n < size) break;
3608 if (n > -1) size = n+1; else size += 4096;
3609 if (xbuf == buf) xbuf = NULL;
3610 if ((t = realloc(xbuf, size)) == NULL) { if (xbuf) free(xbuf); return; }
3611 xbuf = t;
3613 tcmdlinemsg(xbuf);
3614 if (xbuf != buf) free(xbuf);
3618 static void tcmdlineinitex (const char *msg) {
3619 term->cmdMode = CMDMODE_INPUT;
3620 term->cmdofs = 0;
3621 term->cmdline[0] = 0;
3622 term->cmdc[0] = 0;
3623 term->cmdcl = 0;
3624 term->cmdtabpos = -1;
3625 term->cmdprevc = NULL;
3626 term->cmdreslen = 0;
3627 term->cmdexecfn = NULL;
3628 if (msg != NULL && msg[0]) {
3629 strcpy(term->cmdline, msg);
3630 term->cmdreslen = strlen(term->cmdline);
3632 tcmdlinefixofs();
3636 static void tcmdlineinit (void) {
3637 tcmdlineinitex(NULL);
3641 static void tcmdlinechoplast (void) {
3642 if (term->cmdcl != 0) {
3643 term->cmdcl = 0;
3644 } else {
3645 if (strlen(term->cmdline) > term->cmdreslen) utf8choplast(term->cmdline);
3647 tcmdlinefixofs();
3651 // utf-8
3652 static void tcmdaddchar (const char *s) {
3653 int len = utf8size(s);
3655 if (len > 0) {
3656 int slen = strlen(term->cmdline);
3658 if (slen+len < sizeof(term->cmdline)) {
3659 memcpy(term->cmdline+slen, s, len);
3660 term->cmdline[slen+len] = 0;
3661 tcmdlinefixofs();
3667 static void tcmdput (const char *s, int len) {
3668 while (len-- > 0) {
3669 int ok;
3671 term->cmdc[term->cmdcl++] = *s++;
3672 term->cmdc[term->cmdcl] = 0;
3674 if ((ok = isfullutf8(term->cmdc, term->cmdcl)) != 0 || term->cmdcl == UTF_SIZ) {
3675 if (ok) tcmdaddchar(term->cmdc);
3676 term->cmdcl = 0;
3682 ////////////////////////////////////////////////////////////////////////////////
3683 // tty resising
3684 static int tresize (int col, int row) {
3685 int mincol = MIN(col, term->col);
3686 int slide = term->c.y-row+1;
3687 Glyph g;
3689 if (col < 1 || row < 1) return 0;
3691 selhide();
3693 g.state = GLYPH_DIRTY;
3694 g.attr = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG;
3695 g.fg = term->deffg;
3696 g.bg = term->defbg;
3697 g.c[0] = ' ';
3698 g.c[1] = 0;
3700 if (slide > 0) {
3701 tsetscroll(0, term->row-1);
3702 for (; slide > 0; --slide) tscrollup(0, 1, 1); // to fill history
3705 if (row < term->row) {
3706 /* free unneeded rows */
3707 for (int f = row; f < term->row; ++f) free(term->alt[f]);
3708 for (int f = term->linecount-(term->row-row); f < term->linecount; ++f) free(term->line[f]);
3709 term->linecount -= (term->row-row);
3710 /* resize to new height */
3711 term->alt = realloc(term->alt, row*sizeof(Line));
3712 term->line = realloc(term->line, term->linecount*sizeof(Line));
3713 } else if (row > term->row) {
3714 /* resize to new height */
3715 term->alt = realloc(term->alt, row*sizeof(Line));
3716 term->line = realloc(term->line, (row+term->maxhistory)*sizeof(Line));
3717 /* add more lines */
3718 for (int f = term->row; f < row; ++f) {
3719 term->alt[f] = calloc(col, sizeof(Glyph));
3720 for (int x = 0; x < col; ++x) term->alt[f][x] = g;
3722 for (int f = 0; f < row-term->row; ++f) {
3723 int y = term->linecount++;
3725 term->line[y] = calloc(col, sizeof(Glyph));
3726 for (int x = 0; x < col; ++x) term->line[y][x] = g;
3730 if (row != term->row) {
3731 term->dirty = realloc(term->dirty, row*sizeof(*term->dirty));
3734 /* resize each row to new width, zero-pad if needed */
3735 for (int f = 0; f < term->linecount; ++f) {
3736 term->line[f] = realloc(term->line[f], col*sizeof(Glyph));
3737 for (int x = mincol; x < col; ++x) term->line[f][x] = g;
3738 if (f < row) {
3739 markDirty(f, 2);
3740 term->alt[f] = realloc(term->alt[f], col*sizeof(Glyph));
3741 for (int x = mincol; x < col; ++x) term->alt[f][x] = g;
3744 /* update terminal size */
3745 term->topline = 0;
3746 term->col = col;
3747 term->row = row;
3748 /* make use of the LIMIT in tmoveto */
3749 tmoveto(term->c.x, term->c.y);
3750 /* reset scrolling region */
3751 tsetscroll(0, row-1);
3752 tfulldirt();
3753 return (slide > 0);
3757 static void xresize (int col, int row) {
3758 Pixmap newbuf;
3759 int oldw, oldh;
3761 if (term == NULL) return;
3762 oldw = term->picbufw;
3763 oldh = term->picbufh;
3764 term->picbufw = MAX(1, col*xw.cw);
3765 term->picbufh = MAX(1, row*xw.ch);
3766 newbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3767 XCopyArea(xw.dpy, term->picbuf, newbuf, dc.gc, 0, 0, term->picbufw, term->picbufh, 0, 0);
3768 XFreePixmap(xw.dpy, term->picbuf);
3769 XSetForeground(xw.dpy, dc.gc, getColor(term->defbg));
3770 if (term->picbufw > oldw) {
3771 XFillRectangle(xw.dpy, newbuf, dc.gc, oldw, 0, term->picbufw-oldw, MIN(term->picbufh, oldh));
3772 } else if (term->picbufw < oldw && xw.w > term->picbufw) {
3773 XClearArea(xw.dpy, xw.win, term->picbufw, 0, xw.w-term->picbufh, MIN(term->picbufh, oldh), False);
3775 if (term->picbufh > oldh) {
3776 XFillRectangle(xw.dpy, newbuf, dc.gc, 0, oldh, term->picbufw, term->picbufh-oldh);
3777 } else if (term->picbufh < oldh && xw.h > term->picbufh) {
3778 XClearArea(xw.dpy, xw.win, 0, term->picbufh, xw.w, xw.h-term->picbufh, False);
3780 term->picbuf = newbuf;
3781 tfulldirt();
3782 updateTabBar = 1;
3786 ////////////////////////////////////////////////////////////////////////////////
3787 // x11 drawing and utils
3789 static void xcreatebw (void) {
3790 if ((dc.bcol = calloc(MAX_COLOR+1, sizeof(dc.bcol[0]))) == NULL) die("out of memory");
3792 for (int f = 0; f <= MAX_COLOR; ++f) {
3793 XColor nclr;
3795 nclr = dc.ncol[f].pixel;
3796 XQueryColor(xw.dpy, xw.cmap, &nclr);
3797 fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", f, nclr.red, nclr.green, nclr.blue);
3803 static void xallocbwclr (int idx, XColor *color) {
3804 double lumi;
3806 XQueryColor(xw.dpy, xw.cmap, color);
3807 //fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", idx, color->red, color->green, color->blue);
3809 lumi = 0.3*((double)color->red/65535.0)+0.59*((double)color->green/65535.0)+0.11*((double)color->blue/65535.0);
3810 color->red = color->green = color->blue = (int)(lumi*65535.0);
3811 if (!XAllocColor(xw.dpy, xw.cmap, color)) {
3812 fprintf(stderr, "WARNING: could not allocate b/w color #%d\n", idx);
3813 return;
3815 dc.bcol[idx] = color->pixel;
3816 color->red = color->blue = 0;
3817 if (!XAllocColor(xw.dpy, xw.cmap, color)) {
3818 fprintf(stderr, "WARNING: could not allocate b/w color #%d\n", idx);
3819 return;
3821 dc.gcol[idx] = color->pixel;
3825 static void xallocnamedclr (int idx, const char *cname) {
3826 XColor color;
3828 if (!XAllocNamedColor(xw.dpy, xw.cmap, cname, &color, &color)) {
3829 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", idx, cname);
3830 return;
3832 dc.ncol[idx] = color.pixel;
3833 xallocbwclr(idx, &color);
3837 static void xloadcols (void) {
3838 int f, r, g, b;
3839 XColor color;
3840 uint32 white = WhitePixel(xw.dpy, xw.scr);
3842 if ((dc.clrs[0] = dc.ncol = calloc(MAX_COLOR+1, sizeof(dc.ncol[0]))) == NULL) die("out of memory");
3843 if ((dc.clrs[1] = dc.bcol = calloc(MAX_COLOR+1, sizeof(dc.bcol[0]))) == NULL) die("out of memory");
3844 if ((dc.clrs[2] = dc.gcol = calloc(MAX_COLOR+1, sizeof(dc.gcol[0]))) == NULL) die("out of memory");
3846 for (f = 0; f <= MAX_COLOR; ++f) dc.ncol[f] = dc.bcol[f] = white;
3847 /* load colors [0-15] */
3848 for (f = 0; f <= 15; ++f) {
3849 const char *cname = opt_colornames[f]!=NULL?opt_colornames[f]:defcolornames[f];
3851 xallocnamedclr(f, cname);
3853 /* load colors [256-...] */
3854 for (f = 256; f <= MAX_COLOR; ++f) {
3855 const char *cname = opt_colornames[f];
3857 if (cname == NULL) {
3858 if (LEN(defextcolornames) <= f-256) continue;
3859 cname = defextcolornames[f-256];
3861 if (cname == NULL) continue;
3862 xallocnamedclr(f, cname);
3864 /* load colors [16-255] ; same colors as xterm */
3865 for (f = 16, r = 0; r < 6; ++r) {
3866 for (g = 0; g < 6; ++g) {
3867 for (b = 0; b < 6; ++b) {
3868 if (opt_colornames[f] != NULL) {
3869 xallocnamedclr(f, opt_colornames[f]);
3870 } else {
3871 color.red = r == 0 ? 0 : 0x3737+0x2828*r;
3872 color.green = g == 0 ? 0 : 0x3737+0x2828*g;
3873 color.blue = b == 0 ? 0 : 0x3737+0x2828*b;
3874 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3875 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3876 } else {
3877 dc.ncol[f] = color.pixel;
3878 xallocbwclr(f, &color);
3881 ++f;
3885 for (r = 0; r < 24; ++r, ++f) {
3886 if (opt_colornames[f] != NULL) {
3887 xallocnamedclr(f, opt_colornames[f]);
3888 } else {
3889 color.red = color.green = color.blue = 0x0808+0x0a0a*r;
3890 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3891 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3892 } else {
3893 dc.ncol[f] = color.pixel;
3894 xallocbwclr(f, &color);
3899 for (int f = 0; f < LEN(opt_colornames); ++f) if (opt_colornames[f]) free(opt_colornames[f]);
3903 static void xclear (int x1, int y1, int x2, int y2) {
3904 XSetForeground(xw.dpy, dc.gc, getColor(IS_SET(MODE_REVERSE) ? term->deffg : term->defbg));
3905 XFillRectangle(xw.dpy, term->picbuf, dc.gc, x1*xw.cw, y1*xw.ch, (x2-x1+1)*xw.cw, (y2-y1+1)*xw.ch);
3909 static void xhints (void) {
3910 XClassHint class = {opt_class, opt_title};
3911 XWMHints wm = {.flags = InputHint, .input = 1};
3912 XSizeHints size = {
3913 .flags = PSize | PResizeInc | PBaseSize,
3914 .height = xw.h,
3915 .width = xw.w,
3916 .height_inc = xw.ch,
3917 .width_inc = xw.cw,
3918 .base_height = xw.h/*xw.tabheight*/,
3919 .base_width = xw.w,
3921 //XSetWMNormalHints(xw.dpy, xw.win, &size);
3922 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, &size, &wm, &class);
3923 XSetWMProtocols(xw.dpy, xw.win, &XA_WM_DELETE_WINDOW, 1);
3927 static XFontSet xinitfont (const char *fontstr) {
3928 XFontSet set;
3929 char *def, **missing;
3930 int n;
3932 missing = NULL;
3933 set = XCreateFontSet(xw.dpy, fontstr, &missing, &n, &def);
3934 if (missing) {
3935 while (n--) fprintf(stderr, "sterm: missing fontset: %s\n", missing[n]);
3936 XFreeStringList(missing);
3938 return set;
3942 static void xgetfontinfo (XFontSet set, int *ascent, int *descent, short *lbearing, short *rbearing, Font *fid) {
3943 XFontStruct **xfonts;
3944 char **font_names;
3945 int n;
3947 *ascent = *descent = *lbearing = *rbearing = 0;
3948 n = XFontsOfFontSet(set, &xfonts, &font_names);
3949 for (int f = 0; f < n; ++f) {
3950 if (f == 0) *fid = (*xfonts)->fid;
3951 *ascent = MAX(*ascent, (*xfonts)->ascent);
3952 *descent = MAX(*descent, (*xfonts)->descent);
3953 *lbearing = MAX(*lbearing, (*xfonts)->min_bounds.lbearing);
3954 *rbearing = MAX(*rbearing, (*xfonts)->max_bounds.rbearing);
3955 ++xfonts;
3960 static void initfonts (const char *fontstr, const char *bfontstr, const char *tabfont) {
3961 if ((dc.font[0].set = xinitfont(fontstr)) == NULL) {
3962 if ((dc.font[0].set = xinitfont(FONT)) == NULL) die("can't load font %s", fontstr);
3964 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);
3966 if ((dc.font[1].set = xinitfont(bfontstr)) == NULL) {
3967 if ((dc.font[1].set = xinitfont(FONTBOLD)) == NULL) die("can't load font %s", bfontstr);
3969 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);
3971 if ((dc.font[2].set = xinitfont(tabfont)) == NULL) {
3972 if ((dc.font[2].set = xinitfont(FONTTAB)) == NULL) die("can't load font %s", tabfont);
3974 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);
3978 static void xinit (void) {
3979 XSetWindowAttributes attrs;
3980 Window parent;
3981 XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
3983 if (!(xw.dpy = XOpenDisplay(NULL))) die("can't open display");
3985 XA_VT_SELECTION = XInternAtom(xw.dpy, "_STERM_SELECTION_", 0);
3986 XA_CLIPBOARD = XInternAtom(xw.dpy, "CLIPBOARD", 0);
3987 XA_UTF8 = XInternAtom(xw.dpy, "UTF8_STRING", 0);
3988 XA_NETWM_NAME = XInternAtom(xw.dpy, "_NET_WM_NAME", 0);
3989 XA_TARGETS = XInternAtom(xw.dpy, "TARGETS", 0);
3990 XA_WM_DELETE_WINDOW = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", 0);
3991 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
3993 xw.scr = XDefaultScreen(xw.dpy);
3994 /* font */
3995 initfonts(opt_fontnorm, opt_fontbold, opt_fonttab);
3996 /* XXX: Assuming same size for bold font */
3997 xw.cw = dc.font[0].rbearing-dc.font[0].lbearing;
3998 xw.ch = dc.font[0].ascent+dc.font[0].descent;
3999 xw.tch = dc.font[2].ascent+dc.font[2].descent;
4000 xw.tabheight = opt_disabletabs ? 0 : xw.tch+2;
4001 //xw.tabheight = 0;
4002 /* colors */
4003 xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
4004 xloadcols();
4005 /* window - default size */
4006 term->picbufh = term->row*xw.ch;
4007 term->picbufw = term->col*xw.cw;
4009 xw.h = term->picbufh+xw.tabheight;
4010 xw.w = term->picbufw;
4012 attrs.background_pixel = getColor(defaultBG);
4013 attrs.border_pixel = getColor(defaultBG);
4014 attrs.bit_gravity = NorthWestGravity;
4015 attrs.event_mask = FocusChangeMask | KeyPressMask
4016 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
4017 | /*ButtonMotionMask*/ PointerMotionMask | ButtonPressMask | ButtonReleaseMask
4018 | EnterWindowMask | LeaveWindowMask;
4019 attrs.colormap = xw.cmap;
4020 //fprintf(stderr, "oe: [%s]\n", opt_embed);
4021 parent = opt_embed ? strtol(opt_embed, NULL, 0) : XRootWindow(xw.dpy, xw.scr);
4022 xw.win = XCreateWindow(xw.dpy, parent, 0, 0,
4023 xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
4024 XDefaultVisual(xw.dpy, xw.scr),
4025 CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask
4026 | CWColormap,
4027 &attrs);
4028 xhints();
4029 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
4030 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
4031 /* input methods */
4032 if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) die("XOpenIM() failed");
4033 xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL);
4034 /* gc */
4035 dc.gc = XCreateGC(xw.dpy, xw.win, 0, NULL);
4036 /* white cursor, black outline */
4037 xw.cursor = XCreateFontCursor(xw.dpy, XC_xterm);
4038 XDefineCursor(xw.dpy, xw.win, xw.cursor);
4039 XRecolorCursor(xw.dpy, xw.cursor,
4040 &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff},
4041 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
4042 fixWindowTitle(term);
4043 //XStoreName(xw.dpy, xw.win, opt_title);
4045 XSetForeground(xw.dpy, dc.gc, 0);
4046 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
4047 if (xw.tabheight > 0) XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
4049 XMapWindow(xw.dpy, xw.win);
4051 #if BLANKPTR_USE_GLYPH_CURSOR
4052 xw.blankPtr = XCreateGlyphCursor(xw.dpy, dc.font[0].fid, dc.font[0].fid, ' ', ' ', &blackcolor, &blackcolor);
4053 #else
4054 static const char cmbmp[1] = {0};
4055 Pixmap pm;
4057 pm = XCreateBitmapFromData(xw.dpy, xw.win, cmbmp, 1, 1);
4058 xw.blankPtr = XCreatePixmapCursor(xw.dpy, pm, pm, &blackcolor, &blackcolor, 0, 0);
4059 XFreePixmap(xw.dpy, pm);
4060 #endif
4062 XSync(xw.dpy, 0);
4066 static void xblankPointer (void) {
4067 if (!ptrBlanked && xw.blankPtr != None) {
4068 ptrBlanked = 1;
4069 XDefineCursor(xw.dpy, xw.win, xw.blankPtr);
4070 XFlush(xw.dpy);
4075 static void xunblankPointer (void) {
4076 if (ptrBlanked && xw.cursor != None) {
4077 ptrBlanked = 0;
4078 XDefineCursor(xw.dpy, xw.win, xw.cursor);
4079 XFlush(xw.dpy);
4080 ptrLastMove = mclock_ticks();
4085 static void xdraws (const char *s, const Glyph *base, int x, int y, int charlen, int bytelen) {
4086 int fg = base->fg, bg = base->bg, temp;
4087 int winx = x*xw.cw, winy = y*xw.ch+dc.font[0].ascent, width = charlen*xw.cw;
4088 XFontSet fontset = dc.font[0].set;
4089 int defF = base->attr&ATTR_DEFFG, defB = base->attr&ATTR_DEFBG;
4091 /* only switch default fg/bg if term is in RV mode */
4092 if (IS_SET(MODE_REVERSE)) {
4093 if (defF) fg = term->defbg;
4094 if (defB) bg = term->deffg;
4096 if (base->attr&ATTR_REVERSE) defF = defB = 0;
4097 if (base->attr&ATTR_BOLD) {
4098 if (defF && defB && defaultBoldFG >= 0) fg = defaultBoldFG;
4099 else if (fg < 8) fg += 8;
4100 fontset = dc.font[1].set;
4102 if ((base->attr&ATTR_UNDERLINE) && defaultUnderlineFG >= 0) {
4103 if (defF && defB) fg = defaultUnderlineFG;
4106 if (base->attr&ATTR_REVERSE) { temp = fg; fg = bg; bg = temp; }
4108 XSetBackground(xw.dpy, dc.gc, getColor(bg));
4109 XSetForeground(xw.dpy, dc.gc, getColor(fg));
4113 FILE *fo = fopen("zlog.log", "ab");
4114 fprintf(fo, "xdraws: x=%d; y=%d; charlen=%d; bytelen=%d\n", x, y, charlen, bytelen);
4115 fclose(fo);
4119 if (IS_GFX(base->attr)) {
4120 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
4121 } else if (!needConversion) {
4122 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
4123 } else {
4124 if (bytelen > 0) {
4125 //k8: dunno why, but Xutf8DrawImageString() ignores ascii (at least for terminus)
4126 const char *pos = s;
4127 int xpos = winx;
4129 while (pos < s+bytelen) {
4130 const char *e;
4131 int clen;
4133 if ((unsigned char)(pos[0]) < 128) {
4134 for (e = pos+1; e < s+bytelen && (unsigned char)(*e) < 128; ++e) ;
4135 clen = e-pos;
4136 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
4139 FILE *fo = fopen("zlog.log", "ab");
4140 fprintf(fo, " norm: clen=%d (%d); [", clen, (int)(e-pos));
4141 fwrite(pos, 1, e-pos, fo);
4142 fprintf(fo, "]\n");
4143 fclose(fo);
4146 } else {
4147 for (clen = 0, e = pos; e < s+bytelen && (unsigned char)(*e) >= 128; ++e) {
4148 if (((unsigned char)(e[0])&0xc0) == 0xc0) ++clen;
4150 Xutf8DrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
4153 FILE *fo = fopen("zlog.log", "ab");
4154 fprintf(fo, " utf8: clen=%d (%d); [", clen, (int)(e-pos));
4155 fwrite(pos, 1, e-pos, fo);
4156 fprintf(fo, "]\n");
4157 fclose(fo);
4161 xpos += xw.cw*clen;
4162 pos = e;
4167 if (opt_drawunderline && (base->attr&ATTR_UNDERLINE)) {
4168 XDrawLine(xw.dpy, term->picbuf, dc.gc, winx, winy+1, winx+width-1, winy+1);
4173 /* copy buffer pixmap to screen pixmap */
4174 static void xcopy (int x, int y, int cols, int rows) {
4175 int src_x = x*xw.cw, src_y = y*xw.ch, src_w = cols*xw.cw, src_h = rows*xw.ch;
4176 int dst_x = src_x, dst_y = src_y;
4178 if (opt_tabposition == 1) { dst_y += xw.tabheight; }
4179 XCopyArea(xw.dpy, term->picbuf, xw.win, dc.gc, src_x, src_y, src_w, src_h, dst_x, dst_y);
4183 static void xdrawcursor (void) {
4184 Glyph g;
4185 int sl, scrx, scry, cmy;
4187 if (term == NULL) return;
4189 LIMIT(term->oldcx, 0, term->col-1);
4190 LIMIT(term->oldcy, 0, term->row-1);
4192 cmy = term->row-term->topline-1;
4194 if (!(xw.state&WIN_FOCUSED) && !term->curblinkinactive) term->curbhidden = 0;
4196 if (term->cmdMode == CMDMODE_NONE || term->oldcy != cmy) {
4197 scrx = term->oldcx;
4198 scry = term->oldcy+term->topline;
4199 if (scry >= 0 && scry < term->row) {
4200 if (term->curbhidden < 0 ||
4201 term->oldcy != term->c.y || term->oldcx != term->c.x ||
4202 (term->c.state&CURSOR_HIDE) ||
4203 !(xw.state&WIN_FOCUSED)) {
4204 /* remove the old cursor */
4205 sl = utf8size(term->line[term->oldcy][scrx].c);
4206 g = term->line[term->oldcy][scrx];
4207 if (selected(scrx, term->c.y)) g.attr ^= ATTR_REVERSE;
4208 xdraws(g.c, &g, scrx, scry, 1, sl);
4209 //xclear(scrx, term->oldcy, scrx, term->oldcy);
4210 xcopy(scrx, scry, 1, 1);
4212 if (term->curbhidden) term->curbhidden = 1;
4216 if (term->cmdMode != CMDMODE_NONE && term->oldcy == cmy) return;
4217 if ((term->c.state&CURSOR_HIDE) != 0) return;
4218 if (term->curbhidden) return;
4219 /* draw the new one */
4220 scrx = term->c.x;
4221 scry = term->c.y+term->topline;
4222 if (scry >= 0 && scry < term->row) {
4223 int nodraw = 0;
4225 if (!(xw.state&WIN_FOCUSED)) {
4226 if (defaultCursorInactiveBG < 0) {
4227 XSetForeground(xw.dpy, dc.gc, getColor(defaultCursorBG));
4228 XDrawRectangle(xw.dpy, term->picbuf, dc.gc, scrx*xw.cw, scry*xw.ch, xw.cw-1, xw.ch-1);
4229 nodraw = 1;
4230 } else {
4231 g.bg = defaultCursorInactiveBG;
4232 g.fg = defaultCursorInactiveFG;
4234 } else {
4235 g.fg = defaultCursorFG;
4236 g.bg = defaultCursorBG;
4238 if (!nodraw) {
4239 g.uc = term->line[term->c.y][scrx].uc;
4240 g.state = 0;
4241 g.attr = ATTR_NULL;
4242 if (IS_SET(MODE_REVERSE)) g.attr |= ATTR_REVERSE;
4243 sl = utf8size(g.c);
4244 xdraws(g.c, &g, scrx, scry, 1, sl);
4246 term->oldcx = scrx;
4247 term->oldcy = term->c.y;
4248 xcopy(scrx, scry, 1, 1);
4253 static void xdrawTabBar (void) {
4254 if (xw.tabheight > 0 && updateTabBar) {
4255 if (updateTabBar > 0) {
4256 int tabw = xw.w/opt_tabcount, n = (opt_tabposition == 0 ? 1 : 0);
4257 XFontSet fontset = dc.font[2].set;
4259 XSetForeground(xw.dpy, dc.gc, getColor(normalTabBG));
4260 XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
4262 for (int f = firstVisibleTab; f < firstVisibleTab+opt_tabcount; ++f) {
4263 int x = (f-firstVisibleTab)*tabw;
4264 const char *title;
4265 char *tit;
4267 if (f >= term_count) {
4268 XSetForeground(xw.dpy, dc.gc, getColor(normalTabBG));
4269 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, xw.w, xw.tabheight);
4271 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
4272 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
4273 break;
4275 title = term_array[f]->title;
4276 if (!title[0]) title = opt_title;
4277 tit = SPrintf("[%d]%s", f, title);
4278 title = tit;
4280 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
4281 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, tabw, xw.tabheight);
4283 XSetBackground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
4284 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabFG : normalTabFG));
4286 if (needConversion) {
4287 int xx = x+2;
4289 while (*title && xx < x+tabw) {
4290 const char *e = title;
4291 XRectangle r;
4293 memset(&r, 0, sizeof(r));
4295 if ((unsigned char)(*e) > 127) {
4296 while (*e && (unsigned char)(*e) > 127) ++e;
4297 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+n, title, e-title);
4298 Xutf8TextExtents(fontset, title, e-title, &r, NULL);
4299 } else {
4300 while (*e && (unsigned char)(*e) <= 127) ++e;
4301 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+n, title, e-title);
4302 XmbTextExtents(fontset, title, e-title, &r, NULL);
4304 title = e;
4305 xx += r.width-r.x;
4308 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+n, title, strlen(title));
4309 } else {
4310 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+n, title, strlen(title));
4312 free(tit);
4314 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
4315 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x+tabw-2, 0, 2, xw.tabheight);
4317 if (f > firstVisibleTab) {
4318 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
4319 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
4323 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
4324 //XDrawRectangle(xw.dpy, xw.pictab, dc.gc, -1, 0, xw.w+1, xw.tabheight+1);
4325 if (opt_tabposition == 0) {
4326 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, 0);
4327 } else {
4328 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, xw.tabheight-1, xw.w, xw.tabheight-1);
4332 if (opt_tabposition == 0) {
4333 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, xw.h-xw.tabheight);
4334 } else {
4335 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, 0);
4338 updateTabBar = 0;
4342 static void drawcmdline (int scry) {
4343 Glyph base;
4344 int cpos = term->cmdofs, bc = 0, x, sx;
4345 int back = (term->cmdMode == CMDMODE_INPUT ? 21 : 124);
4346 int rslen = (term->cmdMode == CMDMODE_INPUT ? term->cmdreslen : 32760);
4348 base.attr = ATTR_NULL;
4349 base.fg = 255;
4350 base.bg = back;
4351 base.state = 0;
4352 // hilighted
4353 for (sx = x = 0; x < term->col && term->cmdline[cpos] && cpos < rslen; ++x) {
4354 int l = utf8size(term->cmdline+cpos);
4356 if (bc+l > DRAW_BUF_SIZ) {
4357 xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
4358 bc = 0;
4359 sx = x;
4361 memcpy(term->drawbuf+bc, term->cmdline+cpos, l);
4362 cpos += l;
4363 bc += l;
4365 if (bc > 0) xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
4366 // user input
4367 bc = 0;
4368 base.fg = 220;
4369 for (sx = x; x < term->col && term->cmdline[cpos]; ++x) {
4370 int l = utf8size(term->cmdline+cpos);
4372 if (bc+l > DRAW_BUF_SIZ) {
4373 xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
4374 bc = 0;
4375 sx = x;
4377 memcpy(term->drawbuf+bc, term->cmdline+cpos, l);
4378 cpos += l;
4379 bc += l;
4381 if (bc > 0) xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
4383 if (x < term->col && term->cmdMode == CMDMODE_INPUT) {
4384 base.bg = base.fg;
4385 base.fg = back;
4386 xdraws(" ", &base, x, scry, 1, 1);
4387 ++x;
4390 if (x < term->col) {
4391 base.fg = 255;
4392 base.bg = back;
4393 memset(term->drawbuf, ' ', DRAW_BUF_SIZ);
4394 while (x < term->col) {
4395 sx = x;
4396 x += DRAW_BUF_SIZ;
4397 if (x > term->col) x = term->col;
4398 xdraws(term->drawbuf, &base, sx, scry, x-sx, x-sx);
4402 xcopy(0, scry, term->col, 1);
4406 static void drawline (int x1, int x2, int scry, int lineno, int dontcopy) {
4407 //fprintf(stderr, "%d: drawline: x1=%d; x2=%d; scry=%d; row:%d; lineno=%d\n", mclock_ticks(), x1, x2, scry, term->row, lineno);
4408 if (scry < 0 || scry >= term->row) return;
4409 if (scry == term->row-1 && term->cmdMode != CMDMODE_NONE) { drawcmdline(scry); return; }
4410 if (lineno < 0 || lineno >= term->linecount) {
4411 xclear(0, scry, term->col-1, scry);
4412 xcopy(0, scry, term->col, 1);
4413 } else {
4414 int ic, ib, ox, sl;
4415 int stx, ex;
4416 Glyph base, new;
4417 Line l = term->line[lineno];
4419 if (lineno < term->row && term->topline == 0) {
4420 if (!term->dirty[lineno]) return;
4421 if (!dontcopy) {
4422 // fix 'dirty' flag for line
4423 if (term->dirty[lineno]&0x02) {
4424 // mark full line as dirty
4425 stx = 0;
4426 ex = term->col;
4427 term->dirty[lineno] = 0;
4428 } else {
4429 term->dirty[lineno] = 0;
4430 if (x1 > 0) for (int x = 0; x < x1; ++x) if (l[x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
4431 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; }
4433 // find dirty region
4434 for (stx = x1; stx < x2; ++stx) if (l[stx].state&GLYPH_DIRTY) break;
4435 for (ex = x2; ex > stx; --ex) if (l[ex-1].state&GLYPH_DIRTY) break;
4436 if (stx >= x2 || ex <= stx) return; // nothing to do
4438 } else {
4439 // 'dontcopy' means that the whole screen is dirty
4440 stx = 0;
4441 ex = term->col;
4442 term->dirty[lineno] = 0;
4444 } else {
4445 //if (lineno < term->row) term->dirty[lineno] = 0;
4446 stx = 0;
4447 ex = term->col;
4450 base = l[stx];
4451 if (term->sel.bx != -1 && selected(stx, lineno)) base.attr ^= ATTR_REVERSE;
4452 ic = ib = 0;
4453 ox = stx;
4454 //xclear(stx, scry, ex-1, scry); //k8: actually, we don't need this for good monospace fonts, so...
4455 for (int x = stx; x < ex; ++x) {
4456 new = l[x];
4457 l[x].state &= ~GLYPH_DIRTY; //!
4458 if (term->sel.bx != -1 && selected(x, lineno)) new.attr ^= ATTR_REVERSE;
4459 if (ib > 0 && (ATTRCMP(base, new) || ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
4460 // flush draw buffer
4461 xdraws(term->drawbuf, &base, ox, scry, ic, ib);
4462 ic = ib = 0;
4464 if (ib == 0) { ox = x; base = new; }
4465 sl = utf8size(new.c);
4466 memcpy(term->drawbuf+ib, new.c, sl);
4467 ib += sl;
4468 ++ic;
4470 if (ib > 0) xdraws(term->drawbuf, &base, ox, scry, ic, ib);
4471 //xcopy(0, scry, term->col, 1);
4472 //if (term->c.y == lineno && term->c.x >= stx && term->c.x < ex) xdrawcursor();
4473 if (!dontcopy) xcopy(stx, scry, ex-stx, 1);
4478 static void drawregion (int x1, int y1, int x2, int y2, int forced) {
4479 int fulldirty = 0;
4481 if (!forced && (xw.state&WIN_VISIBLE) == 0) {
4482 //dlogf("invisible");
4483 lastDrawTime = term->lastDrawTime = 1;
4484 term->wantRedraw = 1;
4485 return;
4488 if (y1 == 0 && y2 == term->row) {
4489 fulldirty = 1;
4490 for (int y = 0; y < y2; ++y) if (!(term->dirty[y]&0x02)) { fulldirty = 0; break; }
4493 if (term->topline < term->row) {
4494 for (int y = y1; y < y2; ++y) drawline(x1, x2, y+term->topline, y, fulldirty);
4496 if (term->topline > 0) {
4497 int scry = MIN(term->topline, term->row), y = term->row;
4499 fulldirty = 1;
4500 if (term->topline >= term->row) y += term->topline-term->row;
4501 while (--scry >= 0) {
4502 drawline(0, term->col, scry, y, 0);
4503 ++y;
4506 if (fulldirty) xcopy(0, 0, term->col, term->row);
4507 xdrawcursor();
4508 xdrawTabBar();
4509 //XFlush(xw.dpy);
4510 lastDrawTime = term->lastDrawTime = mclock_ticks();
4511 term->wantRedraw = 0;
4515 static void draw (int forced) {
4516 if (term != NULL) {
4517 //fprintf(stderr, "draw(%d) (%d)\n", forced, mclock_ticks());
4518 drawregion(0, 0, term->col, term->row, forced);
4524 static int xisyinunused (int y) {
4525 if (xw.tabheight > 0 && term != NULL) {
4526 int unh = xw.h-(term->row*xw.ch)-xw.tabheight;
4528 if (unh > 0) {
4529 switch (opt_tabposition) {
4530 case 0: // bottom
4531 return (y >= xw.h-xw.tabheight-unh && y < xw.h-xw.tabheight);
4532 break;
4533 case 1: // top
4534 return (y >= xw.tabheight+(term->row*xw.ch)t);
4535 break;
4539 return 0;
4544 static void xclearunused (void) {
4545 if (xw.tabheight > 0 && term != NULL) {
4546 int unh = xw.h-(term->row*xw.ch)-xw.tabheight;
4548 if (unh > 0) {
4549 switch (opt_tabposition) {
4550 case 0: // bottom
4551 XClearArea(xw.dpy, xw.win, 0, xw.h-xw.tabheight-unh, xw.w, unh, False);
4552 break;
4553 case 1: // top
4554 XClearArea(xw.dpy, xw.win, 0, xw.tabheight+(term->row*xw.ch), xw.w, unh, False);
4555 break;
4562 static void expose (XEvent *ev) {
4563 XExposeEvent *e = &ev->xexpose;
4565 if (xw.state&WIN_REDRAW) {
4566 if (!e->count && term != NULL) {
4567 xw.state &= ~WIN_REDRAW;
4568 xcopy(0, 0, term->col, term->row);
4569 updateTabBar = -1;
4571 xclearunused();
4572 } else if (term != NULL) {
4573 int taby = (opt_tabposition==1 ? 0 : xw.h-xw.tabheight);
4574 int termy = (opt_tabposition==1 ? xw.tabheight : 0);
4575 int x0 = e->x/xw.cw, y0 = (e->y-termy)/xw.ch;
4576 int x1 = (e->x+e->width)/xw.cw, y1 = (e->y-termy+e->height)/xw.ch;
4578 //fprintf(stderr, "x=%d; y=%d; w=%d; h=%d\n", e->x, e->y, e->width, e->height);
4579 //fprintf(stderr, "taby=%d; tabh=%d\n", taby, xw.tabheight);
4580 //fprintf(stderr, "x0=%d; y0=%d; x1=%d; y1=%d\n", x0, y0, x1, y1);
4582 if (e->y <= taby+xw.tabheight && e->y+e->height >= taby) {
4583 //fprintf(stderr, "tabbar!\n");
4584 updateTabBar = -1;
4586 //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)));
4587 //xcopy(0, 0, term->col, term->row);
4588 //xclear(x0, y0, x1-x0+1, y1-y0+1);
4589 LIMIT(x0, 0, term->col-1);
4590 LIMIT(x1, 0, term->col-1);
4591 LIMIT(y0, 0, term->row-1);
4592 LIMIT(y1, 0, term->row-1);
4593 //fprintf(stderr, "*:x0=%d; y0=%d; x1=%d; y1=%d\n", x0, y0, x1, y1);
4594 xcopy(x0, y0, x1-x0+1, y1-y0+1);
4595 //xclearunused(); //FIXME: optimize this
4596 } else {
4597 updateTabBar = -1;
4599 xdrawTabBar();
4600 //XFlush(xw.dpy);
4604 static void visibility (XEvent *ev) {
4605 XVisibilityEvent *e = &ev->xvisibility;
4607 if (e->state == VisibilityFullyObscured) xw.state &= ~WIN_VISIBLE;
4608 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 */
4612 static void unmap (XEvent *ev) {
4613 xw.state &= ~WIN_VISIBLE;
4617 static void xseturgency (int add) {
4618 XWMHints *h = XGetWMHints(xw.dpy, xw.win);
4620 h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
4621 XSetWMHints(xw.dpy, xw.win, h);
4622 XFree(h);
4626 static void focus (XEvent *ev) {
4627 if (ev->type == FocusIn) {
4628 xw.state |= WIN_FOCUSED;
4629 xseturgency(0);
4630 tsendfocusevent(1);
4631 } else {
4632 xw.state &= ~WIN_FOCUSED;
4633 tsendfocusevent(0);
4635 //draw(1);
4636 updateTabBar = -1;
4637 xdrawTabBar();
4638 xdrawcursor();
4639 //xcopy(0, 0, term->col, term->row);
4643 ////////////////////////////////////////////////////////////////////////////////
4644 // keyboard mapping
4645 static const char *kmap (KeySym k, uint state) {
4646 const char *res = NULL;
4648 state &= ~Mod2Mask; // numlock
4649 for (int f = 0; f < keymap_used; ++f) {
4650 uint mask = keymap[f].mask;
4652 if (keymap[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) {
4653 //fprintf(stderr, "kp: %d\n", IS_SET(MODE_APPKEYPAD));
4654 if (!IS_SET(MODE_APPKEYPAD)) {
4655 if (!keymap[f].kp) {
4656 //fprintf(stderr, "nokp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4657 return keymap[f].str; // non-keypad hit
4659 continue;
4661 //fprintf(stderr, "kp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4662 if (keymap[f].kp) return keymap[f].str; // keypad hit
4663 res = keymap[f].str; // kp mode, but non-kp mapping found
4666 return res;
4670 static const char *kbind (KeySym k, uint state) {
4671 state &= ~Mod2Mask; // numlock
4672 for (int f = 0; f < keybinds_used; ++f) {
4673 uint mask = keybinds[f].mask;
4675 if (keybinds[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) return keybinds[f].str;
4677 return NULL;
4681 static KeySym do_keytrans (KeySym ks) {
4682 for (int f = 0; f < keytrans_used; ++f) if (keytrans[f].src == ks) return keytrans[f].dst;
4683 return ks;
4687 static void cmdline_closequeryexec (int cancelled) {
4688 if (!cancelled) {
4689 char *rep = term->cmdline+term->cmdreslen;
4691 trimstr(rep);
4692 if (!rep[0] || strchr("yt", tolower(rep[0])) != NULL) {
4693 closeRequestComes = 2;
4694 return;
4697 closeRequestComes = 0;
4701 static void kpress (XEvent *ev) {
4702 XKeyEvent *e = &ev->xkey;
4703 KeySym ksym = NoSymbol;
4704 const char *kstr;
4705 int len;
4706 Status status;
4707 char buf[32];
4709 if (term == NULL) return;
4711 if (!ptrBlanked && opt_ptrblank > 0) xblankPointer();
4713 //if (len < 1) len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
4714 if ((len = Xutf8LookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status)) > 0) buf[len] = 0;
4715 // leave only known mods
4716 e->state &= (Mod1Mask | Mod4Mask | ControlMask | ShiftMask);
4717 #ifdef DUMP_KEYSYMS
4719 const char *ksname = XKeysymToString(ksym);
4721 fprintf(stderr, "utf(%d):[%s] (%s) 0x%08x\n", len, len>=0?buf:"<shit>", ksname, (unsigned int)e->state);
4723 #endif
4724 if ((kstr = kbind(ksym, e->state)) != NULL) {
4725 // keybind found
4726 executeCommands(kstr);
4727 return;
4730 if (term->cmdMode != CMDMODE_NONE) {
4731 int mode = term->cmdMode;
4733 switch (do_keytrans(ksym)) {
4734 case XK_Return:
4735 tcmdlinehide();
4736 if (term->cmdexecfn) term->cmdexecfn(0);
4737 else if (mode == CMDMODE_INPUT) executeCommands(term->cmdline);
4738 break;
4739 case XK_BackSpace:
4740 if (mode == CMDMODE_INPUT) {
4741 tcmdlinechoplast();
4742 term->cmdtabpos = -1;
4743 term->cmdprevc = NULL;
4744 } else {
4745 tcmdlinehide();
4746 if (term->cmdexecfn) term->cmdexecfn(1);
4748 break;
4749 case XK_Escape:
4750 tcmdlinehide();
4751 if (term->cmdexecfn) term->cmdexecfn(1);
4752 break;
4753 case XK_Tab:
4754 if (mode == CMDMODE_INPUT && term->cmdline[0] && term->cmdcl == 0 && term->cmdexecfn == NULL) {
4755 const char *cpl;
4757 if (term->cmdtabpos < 0) {
4758 term->cmdtabpos = 0;
4759 while (term->cmdline[term->cmdtabpos] && isalnum(term->cmdline[term->cmdtabpos])) ++term->cmdtabpos;
4760 if (term->cmdline[term->cmdtabpos]) {
4761 term->cmdtabpos = -1;
4762 break;
4764 term->cmdprevc = NULL;
4766 cpl = findCommandCompletion(term->cmdline, term->cmdtabpos, term->cmdprevc);
4767 if (cpl == NULL && term->cmdprevc != NULL) cpl = findCommandCompletion(term->cmdline, term->cmdtabpos, NULL);
4768 term->cmdprevc = cpl;
4769 if (cpl != NULL) strcpy(term->cmdline, cpl);
4770 tcmdlinefixofs();
4771 } else if (mode != CMDMODE_INPUT) {
4772 tcmdlinehide();
4773 if (term->cmdexecfn) term->cmdexecfn(1);
4775 break;
4776 case XK_space:
4777 if (mode != CMDMODE_INPUT) {
4778 tcmdlinehide();
4779 break;
4781 // fallthru
4782 default:
4783 if (mode == CMDMODE_INPUT) {
4784 if (len > 0 && (unsigned char)buf[0] >= 32) {
4785 tcmdput(buf, len);
4786 term->cmdtabpos = -1;
4787 term->cmdprevc = NULL;
4790 break;
4792 return;
4795 if ((kstr = kmap(do_keytrans(ksym), e->state)) != NULL) {
4796 if (kstr[0]) {
4797 tunshowhistory();
4798 ttywritestr(kstr);
4800 } else {
4801 int meta = (e->state&Mod1Mask);
4803 int shift = (e->state&ShiftMask);
4804 int ctrl = (e->state&ControlMask);
4806 switch (ksym) {
4807 case XK_Return:
4808 tunshowhistory();
4809 if (meta) {
4810 ttywritestr("\x1b\x0a");
4811 } else {
4812 if (IS_SET(MODE_CRLF)) ttywritestr("\r\n"); else ttywritestr("\r");
4814 break;
4815 default:
4816 if (len > 0) {
4817 tunshowhistory();
4818 if (meta && len == 1) ttywritestr("\x1b");
4819 ttywrite(buf, len);
4821 break;
4827 ////////////////////////////////////////////////////////////////////////////////
4828 // xembed?
4829 static void cmessage (XEvent *e) {
4830 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
4831 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
4832 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
4833 xw.state |= WIN_FOCUSED;
4834 xseturgency(0);
4835 tsendfocusevent(1);
4836 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
4837 xw.state &= ~WIN_FOCUSED;
4838 tsendfocusevent(0);
4840 xdrawcursor();
4841 xdrawTabBar();
4842 xcopy(0, 0, term->col, term->row);
4843 return;
4846 if (e->xclient.data.l[0] == XA_WM_DELETE_WINDOW) {
4847 closeRequestComes = 1;
4848 return;
4853 ////////////////////////////////////////////////////////////////////////////////
4854 static void resize (XEvent *e) {
4855 int col, row;
4856 Term *ot = term;
4858 //if (e->xconfigure.width == 65535 || e->xconfigure.width == -1) e->xconfigure.width = xw.w;
4859 if (e->xconfigure.height == 65535 || e->xconfigure.height == -1) e->xconfigure.height = xw.h;
4860 //if ((short int)e->xconfigure.height < xw.ch) return;
4862 if (e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) return;
4863 xw.w = e->xconfigure.width;
4864 xw.h = e->xconfigure.height;
4865 col = xw.w/xw.cw;
4866 row = (xw.h-xw.tabheight)/xw.ch;
4867 //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);
4868 if (col == term->col && row == term->row) return;
4869 for (int f = 0; f < term_count; ++f) {
4870 term = term_array[f];
4871 if (tresize(col, row) && ot == term) draw(1);
4872 ttyresize();
4873 xresize(col, row);
4875 term = ot;
4876 XFreePixmap(xw.dpy, xw.pictab);
4877 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
4878 updateTabBar = 1;
4882 static inline int last_draw_too_old (void) {
4883 int tt = mclock_ticks();
4885 if (term != NULL) {
4886 if (term->dead || !term->wantRedraw) return 0;
4887 return (tt-term->lastDrawTime >= opt_drawtimeout);
4889 return (tt-lastDrawTime >= opt_maxdrawtimeout);
4893 ////////////////////////////////////////////////////////////////////////////////
4894 // main loop
4895 static void (*handler[LASTEvent])(XEvent *) = {
4896 [KeyPress] = kpress,
4897 [ClientMessage] = cmessage,
4898 [ConfigureNotify] = resize,
4899 [VisibilityNotify] = visibility,
4900 [UnmapNotify] = unmap,
4901 [Expose] = expose,
4902 [FocusIn] = focus,
4903 [FocusOut] = focus,
4904 [MotionNotify] = bmotion,
4905 [ButtonPress] = bpress,
4906 [ButtonRelease] = brelease,
4907 [SelectionNotify] = selnotify,
4908 [SelectionRequest] = selrequest,
4909 [SelectionClear] = selclear,
4913 static void run (void) {
4914 //int stuff_to_print = 0;
4915 int xfd = XConnectionNumber(xw.dpy);
4917 ptrLastMove = mclock_ticks();
4918 while (term_count > 0) {
4919 XEvent ev;
4920 fd_set rfd, wfd;
4921 struct timeval timeout;
4922 int maxfd = xfd;
4923 Term *ot;
4924 int dodraw;
4926 FD_ZERO(&rfd);
4927 FD_ZERO(&wfd);
4928 FD_SET(xfd, &rfd);
4929 //FD_SET(term->cmdfd, &rfd);
4930 // have something to write?
4931 for (int f = 0; f < term_count; ++f) {
4932 Term *t = term_array[f];
4934 if (!t->dead && term->cmdfd >= 0 && t->pid != 0) {
4935 if (t->cmdfd > maxfd) maxfd = t->cmdfd;
4936 FD_SET(t->cmdfd, &rfd);
4937 if (t->wrbufpos < t->wrbufused) FD_SET(t->cmdfd, &wfd);
4941 timeout.tv_sec = 0;
4942 timeout.tv_usec = (opt_drawtimeout+2)*1000;
4943 if (select(maxfd+1, &rfd, &wfd, NULL, &timeout) < 0) {
4944 if (errno == EINTR) continue;
4945 die("select failed: %s", SERRNO);
4948 ot = term;
4949 for (int f = 0; f < term_count; ++f) {
4950 Term *t = term_array[f];
4952 if (!t->dead && term->cmdfd >= 0 && term->pid != 0) {
4953 term = t;
4954 if (FD_ISSET(t->cmdfd, &wfd)) ttyflushwrbuf();
4955 if (FD_ISSET(t->cmdfd, &rfd)) ttyread(); //t->wantRedraw = 1;
4956 term = ot;
4960 termcleanup();
4961 if (term_count == 0) exit(exitcode);
4963 dodraw = 0;
4964 if (term != NULL && term->curblink > 0) {
4965 int tt = mclock_ticks();
4967 if (tt-term->lastBlinkTime >= term->curblink) {
4968 term->lastBlinkTime = tt;
4969 if ((xw.state&WIN_FOCUSED) || term->curblinkinactive) {
4970 term->curbhidden = (term->curbhidden ? 0 : -1);
4971 dodraw = 1;
4972 } else {
4973 term->curbhidden = 0;
4977 if (updateTabBar) xdrawTabBar();
4978 if (dodraw || last_draw_too_old()) draw(0);
4980 if (XPending(xw.dpy)) {
4981 while (XPending(xw.dpy)) {
4982 XNextEvent(xw.dpy, &ev);
4983 switch (ev.type) {
4984 //case VisibilityNotify:
4985 //case UnmapNotify:
4986 //case FocusIn:
4987 //case FocusOut:
4988 case MotionNotify:
4989 case ButtonPress:
4990 case ButtonRelease:
4991 xunblankPointer();
4992 ptrLastMove = mclock_ticks();
4993 break;
4994 default: ;
4996 if (XFilterEvent(&ev, xw.win)) continue;
4997 if (handler[ev.type]) (handler[ev.type])(&ev);
5001 if (term != NULL) {
5002 switch (closeRequestComes) {
5003 case 1: // just comes
5004 if (opt_ignoreclose == 0) {
5005 tcmdlinehide();
5006 tcmdlineinitex("Do you really want to close sterm [Y/n]? ");
5007 term->cmdexecfn = cmdline_closequeryexec;
5008 } else if (opt_ignoreclose < 0) {
5009 //FIXME: kill all clients?
5010 return;
5012 closeRequestComes = 0;
5013 break;
5014 case 2: // ok, die now
5015 //FIXME: kill all clients?
5016 return;
5020 if (!ptrBlanked && opt_ptrblank > 0 && mclock_ticks()-ptrLastMove >= opt_ptrblank) {
5021 xblankPointer();
5027 ////////////////////////////////////////////////////////////////////////////////
5028 typedef const char * (*IniHandlerFn) (const char *optname, const char *fmt, char *argstr, void *udata);
5031 typedef struct {
5032 const char *name;
5033 const char *fmt;
5034 void *udata;
5035 IniHandlerFn fn;
5036 } IniCommand;
5039 static const char *inifnGenericOneArg (const char *optname, const char *fmt, char *argstr, void *udata) {
5040 return iniParseArguments(argstr, fmt, udata);
5044 static const char *inifnGenericOneStr (const char *optname, const char *fmt, char *argstr, void *udata) {
5045 char *s = NULL;
5046 const char *err = iniParseArguments(argstr, fmt, &s);
5048 if (err != NULL) return err;
5049 if ((s = strdup(s)) == NULL) return "out of memory";
5050 if (udata) {
5051 char **ustr = (char **)udata;
5053 if (*ustr) free(*ustr);
5054 *ustr = s;
5056 return NULL;
5060 static const char *inifnTabPosition (const char *optname, const char *fmt, char *argstr, void *udata) {
5061 int newpos = -1;
5062 const char *err = NULL;
5064 while (argstr[0]) {
5065 char *s = NULL;
5067 if ((err = iniParseArguments(argstr, "R-", &argstr)) != NULL) return err;
5068 if (!argstr[0]) break;
5070 if ((err = iniParseArguments(argstr, "s!-R-", &s, &argstr)) != NULL) return err;
5071 if (tolower(s[0]) == 't') newpos = 1;
5072 else if (tolower(s[0]) == 'b') newpos = 0;
5073 else if ((err = iniParseArguments(s, fmt, &newpos)) != NULL) return err;
5076 if (newpos == -1) return "invalid tabbar position";
5077 opt_tabposition = newpos;
5078 return NULL;
5082 static const IniCommand iniCommands[] = {
5083 {"term", "s!-", &opt_term, inifnGenericOneStr},
5084 {"class", "s!-", &opt_class, inifnGenericOneStr},
5085 {"title", "s!-", &opt_title, inifnGenericOneStr},
5086 {"fontnorm", "s!-", &opt_fontnorm, inifnGenericOneStr},
5087 {"fontbold", "s!-", &opt_fontbold, inifnGenericOneStr},
5088 {"fonttab", "s!-", &opt_fonttab, inifnGenericOneStr},
5089 {"shell", "s!-", &opt_shell, inifnGenericOneStr},
5090 {"doubleclicktimeout", "i{0,10000}", &opt_doubleclick_timeout, inifnGenericOneArg},
5091 {"tripleclicktimeout", "i{0,10000}", &opt_tripleclick_timeout, inifnGenericOneArg},
5092 {"tabsize", "i{1,256}", &opt_tabsize, inifnGenericOneArg},
5093 {"defaultfg", "i{0,511}", &defaultFG, inifnGenericOneArg},
5094 {"defaultbg", "i{0,511}", &defaultBG, inifnGenericOneArg},
5095 {"defaultcursorfg", "i{0,511}", &defaultCursorFG, inifnGenericOneArg},
5096 {"defaultcursorbg", "i{0,511}", &defaultCursorBG, inifnGenericOneArg},
5097 {"defaultinactivecursorfg", "i{0,511}", &defaultCursorInactiveFG, inifnGenericOneArg},
5098 {"defaultinactivecursorbg", "i{-1,511}", &defaultCursorInactiveBG, inifnGenericOneArg},
5099 {"defaultboldfg", "i{-1,511}", &defaultBoldFG, inifnGenericOneArg},
5100 {"defaultunderlinefg", "i{-1,511}", &defaultUnderlineFG, inifnGenericOneArg},
5101 {"normaltabfg", "i{0,511}", &normalTabFG, inifnGenericOneArg},
5102 {"normaltabbg", "i{0,511}", &normalTabBG, inifnGenericOneArg},
5103 {"activetabfg", "i{0,511}", &activeTabFG, inifnGenericOneArg},
5104 {"activetabbg", "i{0,511}", &activeTabBG, inifnGenericOneArg},
5105 {"maxhistory", "i{0,65535}", &opt_maxhistory, inifnGenericOneArg},
5106 {"ptrblank", "i{0,65535}", &opt_ptrblank, inifnGenericOneArg},
5107 {"tabcount", "i{1,128}", &opt_tabcount, inifnGenericOneArg},
5108 {"tabposition", "i{0,1}", &opt_tabposition, inifnTabPosition},
5109 {"drawtimeout", "i{5,30000}", &opt_drawtimeout, inifnGenericOneArg},
5110 {"audiblebell", "b", &opt_audiblebell, inifnGenericOneArg},
5111 {"urgentbell", "b", &opt_urgentbell, inifnGenericOneArg},
5112 {"cursorblink", "i{0,10000}", &opt_cursorBlink, inifnGenericOneArg},
5113 {"cursorblinkinactive", "b", &opt_cursorBlinkInactive, inifnGenericOneArg},
5114 {"drawunderline", "b", &opt_drawunderline, inifnGenericOneArg},
5115 {"ignoreclose", "i{-1,1}", &opt_ignoreclose, inifnGenericOneArg},
5116 {"maxdrawtimeout", "i{100,60000}", &opt_maxdrawtimeout, inifnGenericOneArg},
5117 {"fastredraw", "b", &opt_fastredraw, inifnGenericOneArg},
5118 {NULL, NULL, NULL, NULL}
5122 #define MISC_CMD_NONE ((const char *)-1)
5125 // NULL: command processed; MISC_CMD_NONE: unknown command; !NULL: error
5126 static const char *processMiscCmds (const char *optname, char *argstr) {
5127 const char *err = NULL;
5129 if (strcasecmp(optname, "unimap") == 0) {
5130 int uni, ch;
5131 char *alt = NULL;
5133 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
5134 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
5135 if (unimap) free(unimap);
5136 unimap = NULL;
5137 return NULL;
5140 //unimap 0x2592 0x61 alt
5141 if ((err = iniParseArguments(argstr, "i{0,65535}i{0,255}|s!-", &uni, &ch, &alt)) != NULL) return err;
5142 if (alt != NULL && strcasecmp(alt, "alt") != 0) return "invalid unimap";
5143 if (unimap == NULL) {
5144 if ((unimap = calloc(65536, sizeof(unimap[0]))) == NULL) return "out of memory";
5146 if (alt != NULL && ch == 0) alt = NULL;
5147 if (alt != NULL && ch < 96) return "invalid unimap";
5148 unimap[uni] = ch;
5149 if (alt != NULL) unimap[uni] |= 0x8000;
5150 return NULL;
5153 if (strcasecmp(optname, "keytrans") == 0) {
5154 char *src = NULL, *dst = NULL;
5156 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
5157 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
5158 keytrans_reset();
5159 return NULL;
5162 if ((err = iniParseArguments(argstr, "s!-s!-", &src, &dst)) != NULL) return err;
5163 keytrans_add(src, dst);
5164 return NULL;
5167 if (strcasecmp(optname, "keybind") == 0) {
5168 char *key = NULL, *act = NULL;
5169 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
5170 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
5171 keybinds_reset();
5172 return NULL;
5175 if ((err = iniParseArguments(argstr, "s!-R!", &key, &act)) != NULL) return err;
5176 if (act[0] == '"') {
5177 char *t;
5179 if ((err = iniParseArguments(act, "s!", &t)) != NULL) return err;
5180 act = t;
5182 keybind_add(key, act);
5183 return NULL;
5186 if (strcasecmp(optname, "keymap") == 0) {
5187 char *key = NULL, *str = NULL;
5189 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
5190 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
5191 keymap_reset();
5192 return NULL;
5195 if ((err = iniParseArguments(argstr, "s!-s!-", &key, &str)) != NULL) return err;
5196 keymap_add(key, str);
5197 return NULL;
5200 return MISC_CMD_NONE;
5204 #define INI_LINE_SIZE (32768)
5206 // <0: file not found
5207 // >0: file loading error
5208 // 0: ok
5209 static int loadConfig (const char *fname) {
5210 int inifelse = 0; // 0: not; 1: doing true; 2: doing false; -1: waiting else/endif; -2: waiting endif
5211 FILE *fi = fopen(fname, "r");
5212 const char *err = NULL;
5213 char *line;
5214 int lineno = 0;
5216 if (fi == NULL) return -1;
5217 if ((line = malloc(INI_LINE_SIZE)) == NULL) { err = "out of memory"; goto quit; }
5219 while (fgets(line, INI_LINE_SIZE-1, fi) != NULL) {
5220 char *optname, *argstr, *d;
5221 int goodoption = 0;
5223 ++lineno;
5224 line[INI_LINE_SIZE-1] = 0;
5225 // get option name
5226 for (optname = line; *optname && isspace(*optname); ++optname) ;
5227 if (!optname[0] || optname[0] == '#') continue; // comment
5228 if (!isalnum(optname[0])) { err = "invalid option name"; goto quit; }
5229 d = argstr = optname;
5230 while (*argstr) {
5231 if (!argstr[0] || isspace(argstr[0])) break;
5232 if (!isalnum(argstr[0]) && argstr[0] != '_' && argstr[0] != '.') { err = "invalid option name"; goto quit; }
5233 if (argstr[0] != '_') *d++ = tolower(*argstr);
5234 ++argstr;
5236 if (*argstr) ++argstr;
5237 *d = 0;
5238 if (!isalnum(optname[0])) { err = "invalid option name"; goto quit; }
5240 if (strcasecmp(optname, "ifterm") == 0) {
5241 char *val;
5243 if (inifelse != 0) { err = "nested ifs are not allowed"; goto quit; }
5244 inifelse = -1;
5245 if ((err = iniParseArguments(argstr, "s", &val)) != NULL) goto quit;
5246 if (strcasecmp(opt_term, val) == 0) inifelse = 1;
5247 continue;
5249 if (strcasecmp(optname, "else") == 0) {
5250 switch (inifelse) {
5251 case -1: inifelse = 2; break;
5252 case 2: case -2: case 0: err = "else without if"; goto quit;
5253 case 1: inifelse = -2; break;
5255 continue;
5257 if (strcasecmp(optname, "endif") == 0) {
5258 switch (inifelse) {
5259 case -1: case -2: case 1: case 2: inifelse = 0; break;
5260 case 0: err = "endif without if"; goto quit;
5262 continue;
5265 if (inifelse < 0) {
5266 //trimstr(argstr);
5267 //fprintf(stderr, "skip: [%s]\n", argstr);
5268 continue;
5270 if (opt_term_locked && strcasecmp(optname, "term") == 0) continue; // termname given in command line
5271 // ok, we have option name in `optname` and arguments in `argstr`
5272 if (strncmp(optname, "color.", 6) == 0) {
5273 int n = 0;
5274 char *s = NULL;
5276 optname += 6;
5277 if (!optname[0]) { err = "invalid color option"; goto quit; }
5278 while (*optname) {
5279 if (!isdigit(*optname)) { err = "invalid color option"; goto quit; }
5280 n = (n*10)+(optname[0]-'0');
5281 ++optname;
5283 if (n < 0 || n > 511) { err = "invalid color index"; goto quit; }
5285 if ((err = iniParseArguments(argstr, "s!-", &s)) != NULL) goto quit;
5286 if ((s = strdup(s)) == NULL) { err = "out of memory"; goto quit; }
5287 if (opt_colornames[n] != NULL) free(opt_colornames[n]);
5288 opt_colornames[n] = s;
5289 continue;
5292 if ((err = processMiscCmds(optname, argstr)) != MISC_CMD_NONE) {
5293 if (err != NULL) goto quit;
5294 continue;
5295 } else {
5296 err = NULL;
5299 for (int f = 0; iniCommands[f].name != NULL; ++f) {
5300 if (strcmp(iniCommands[f].name, optname) == 0) {
5301 if ((err = iniCommands[f].fn(optname, iniCommands[f].fmt, argstr, iniCommands[f].udata)) != NULL) goto quit;
5302 goodoption = 1;
5303 break;
5306 if (!goodoption) {
5307 fprintf(stderr, "ini error at line %d: unknown option '%s'!\n", lineno, optname);
5310 quit:
5311 if (line != NULL) free(line);
5312 fclose(fi);
5313 if (err == NULL && inifelse != 0) err = "if without endif";
5314 if (err != NULL) die("ini error at line %d: %s", lineno, err);
5315 return 0;
5319 static void initDefaultOptions (void) {
5320 opt_title = strdup("sterm");
5321 opt_class = strdup("sterm");
5322 opt_term = strdup(TNAME);
5323 opt_fontnorm = strdup(FONT);
5324 opt_fontbold = strdup(FONTBOLD);
5325 opt_fonttab = strdup(FONTTAB);
5326 opt_shell = strdup(SHELL);
5328 memset(opt_colornames, 0, sizeof(opt_colornames));
5329 for (int f = 0; f < LEN(defcolornames); ++f) opt_colornames[f] = strdup(defcolornames[f]);
5330 for (int f = 0; f < LEN(defextcolornames); ++f) opt_colornames[f+256] = strdup(defextcolornames[f]);
5332 keytrans_add("KP_Home", "Home");
5333 keytrans_add("KP_Left", "Left");
5334 keytrans_add("KP_Up", "Up");
5335 keytrans_add("KP_Right", "Right");
5336 keytrans_add("KP_Down", "Down");
5337 keytrans_add("KP_Prior", "Prior");
5338 keytrans_add("KP_Next", "Next");
5339 keytrans_add("KP_End", "End");
5340 keytrans_add("KP_Begin", "Begin");
5341 keytrans_add("KP_Insert", "Insert");
5342 keytrans_add("KP_Delete", "Delete");
5344 keybind_add("shift+Insert", "PastePrimary");
5345 keybind_add("alt+Insert", "PasteCliboard");
5346 keybind_add("ctrl+alt+t", "NewTab");
5347 keybind_add("ctrl+alt+Left", "SwitchToTab prev");
5348 keybind_add("ctrl+alt+Right", "SwitchToTab next");
5350 keymap_add("BackSpace", "\177");
5351 keymap_add("Insert", "\x1b[2~");
5352 keymap_add("Delete", "\x1b[3~");
5353 keymap_add("Home", "\x1b[1~");
5354 keymap_add("End", "\x1b[4~");
5355 keymap_add("Prior", "\x1b[5~");
5356 keymap_add("Next", "\x1b[6~");
5357 keymap_add("F1", "\x1bOP");
5358 keymap_add("F2", "\x1bOQ");
5359 keymap_add("F3", "\x1bOR");
5360 keymap_add("F4", "\x1bOS");
5361 keymap_add("F5", "\x1b[15~");
5362 keymap_add("F6", "\x1b[17~");
5363 keymap_add("F7", "\x1b[18~");
5364 keymap_add("F8", "\x1b[19~");
5365 keymap_add("F9", "\x1b[20~");
5366 keymap_add("F10", "\x1b[21~");
5367 keymap_add("Up", "\x1bOA");
5368 keymap_add("Down", "\x1bOB");
5369 keymap_add("Right", "\x1bOC");
5370 keymap_add("Left", "\x1bOD");
5374 ////////////////////////////////////////////////////////////////////////////////
5375 static Term *oldTerm;
5376 static int oldTermIdx;
5377 static Term *newTerm;
5378 static int newTermIdx;
5379 static int newTermSwitch;
5382 typedef void (*CmdHandlerFn) (const char *cmdname, char *argstr);
5384 typedef struct {
5385 const char *name;
5386 CmdHandlerFn fn;
5387 } Command;
5390 static void cmdPastePrimary (const char *cmdname, char *argstr) {
5391 selpaste(XA_PRIMARY);
5395 static void cmdPasteSecondary (const char *cmdname, char *argstr) {
5396 selpaste(XA_SECONDARY);
5400 static void cmdPasteClipboard (const char *cmdname, char *argstr) {
5401 selpaste(XA_CLIPBOARD);
5405 static void cmdExec (const char *cmdname, char *argstr) {
5406 if (term != NULL) {
5407 if (term->execcmd != NULL) free(term->execcmd);
5408 term->execcmd = (argstr[0] ? strdup(argstr) : NULL);
5413 static int parseTabArgs (char *argstr, int *noswitch, int nowrap, int idx) {
5414 for (;;) {
5415 char *arg;
5417 while (*argstr && isspace(*argstr)) ++argstr;
5418 if (!argstr[0]) break;
5419 if (iniParseArguments(argstr, "s-R-", &arg, &argstr) != NULL) break;
5421 if (strcasecmp(arg, "noswitch") == 0) *noswitch = 1;
5422 else if (strcasecmp(arg, "switch") == 0) *noswitch = 0;
5423 else if (strcasecmp(arg, "nowrap") == 0) nowrap = 1;
5424 else if (strcasecmp(arg, "wrap") == 0) nowrap = 0;
5425 else if (strcasecmp(arg, "first") == 0) idx = 0;
5426 else if (strcasecmp(arg, "last") == 0) idx = term_count-1;
5427 else if (strcasecmp(arg, "prev") == 0) idx = -1;
5428 else if (strcasecmp(arg, "next") == 0) idx = -2;
5429 else {
5430 long int n = -1;
5431 char *eptr;
5433 n = strtol(arg, &eptr, 0);
5434 if (!eptr[0] && n >= 0 && n < term_count) idx = n;
5437 switch (idx) {
5438 case -1: // prev
5439 if ((idx = termidx-1) < 0) idx = nowrap ? 0 : term_count-1;
5440 break;
5441 case -2: // next
5442 if ((idx = termidx+1) >= term_count) idx = nowrap ? term_count-1 : 0;
5443 break;
5445 return idx;
5449 static void flushNewTerm (void) {
5450 if (newTerm != NULL) {
5451 if (newTermSwitch && term != NULL) term->lastActiveTime = mclock_ticks();
5452 term = newTerm;
5453 termidx = newTermIdx;
5454 tinitialize(term_array[0]->col, term_array[0]->row);
5456 term->picbufh = term->row*xw.ch;
5457 term->picbufw = term->col*xw.cw;
5458 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
5459 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
5461 if (ttynew(term) != 0) {
5462 term = oldTerm;
5463 termidx = oldTermIdx;
5464 termfree(newTermIdx);
5465 } else {
5466 selinit();
5467 ttyresize();
5468 if (newTermSwitch) {
5469 term = NULL;
5470 termidx = 0;
5471 switchToTerm(newTermIdx, 1);
5472 oldTerm = term;
5473 oldTermIdx = termidx;
5474 } else {
5475 term = oldTerm;
5476 termidx = oldTermIdx;
5479 newTerm = NULL;
5484 static void cmdNewTab (const char *cmdname, char *argstr) {
5485 int noswitch = 0, idx;
5487 if (opt_disabletabs) return;
5488 flushNewTerm();
5489 if ((newTerm = termalloc()) == NULL) return;
5490 /*idx =*/ parseTabArgs(argstr, &noswitch, 0, termidx);
5491 idx = term_count-1;
5492 if (!noswitch) {
5493 if (term != NULL) term->lastActiveTime = mclock_ticks();
5494 oldTermIdx = idx;
5496 newTermIdx = termidx = idx;
5497 term = newTerm;
5498 newTermSwitch = !noswitch;
5502 static void cmdCloseTab (const char *cmdname, char *argstr) {
5503 flushNewTerm();
5504 if (term != NULL && !term->dead) kill(term->pid, SIGTERM);
5508 static void cmdKillTab (const char *cmdname, char *argstr) {
5509 flushNewTerm();
5510 if (!term->dead) kill(term->pid, SIGKILL);
5514 static void cmdSwitchToTab (const char *cmdname, char *argstr) {
5515 int noswitch = 0, idx;
5517 flushNewTerm();
5518 idx = parseTabArgs(argstr, &noswitch, 0, -666);
5519 if (idx >= 0) {
5520 switchToTerm(idx, 1);
5521 oldTerm = term;
5522 oldTermIdx = termidx;
5527 static void cmdMoveTabTo (const char *cmdname, char *argstr) {
5528 int noswitch = 0, idx;
5530 flushNewTerm();
5531 idx = parseTabArgs(argstr, &noswitch, 0, termidx);
5532 if (idx != termidx && idx >= 0 && idx < term_count) {
5533 Term *t = term_array[termidx];
5535 // remove current term
5536 for (int f = termidx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
5537 // insert term
5538 for (int f = term_count-2; f >= idx; --f) term_array[f+1] = term_array[f];
5539 term_array[idx] = t;
5540 termidx = idx;
5541 oldTerm = t;
5542 oldTermIdx = idx;
5543 updateTabBar = 1;
5548 static void cmdDefaultFG (const char *cmdname, char *argstr) {
5549 char *s = NULL;
5550 int c;
5552 if (iniParseArguments(argstr, "i{0,511}|s-", &c, &s) == NULL) {
5553 if (s != NULL && tolower(s[0]) == 'g') defaultFG = c; else term->deffg = c;
5558 static void cmdDefaultBG (const char *cmdname, char *argstr) {
5559 char *s = NULL;
5560 int c;
5562 if (iniParseArguments(argstr, "i{0,511}|s-", &c) == NULL) {
5563 if (s != NULL && tolower(s[0]) == 'g') {
5564 defaultBG = c;
5565 } else {
5566 term->defbg = c;
5567 XSetWindowBackground(xw.dpy, xw.win, getColor(term->defbg));
5568 if (newTerm == NULL) {
5569 tfulldirt();
5570 draw(1);
5571 xclearunused();
5578 static void scrollHistory (int delta) {
5579 if (term->maxhistory < 1) return; // no history
5580 term->topline += delta;
5581 if (term->topline > term->maxhistory) term->topline = term->maxhistory;
5582 if (term->topline < 0) term->topline = 0;
5583 tfulldirt();
5584 draw(1);
5588 static void cmdScrollHistoryLineUp (const char *cmdname, char *argstr) {
5589 scrollHistory(1);
5593 static void cmdScrollHistoryPageUp (const char *cmdname, char *argstr) {
5594 scrollHistory(term->row);
5598 static void cmdScrollHistoryLineDown (const char *cmdname, char *argstr) {
5599 scrollHistory(-1);
5603 static void cmdScrollHistoryPageDown (const char *cmdname, char *argstr) {
5604 scrollHistory(-term->row);
5608 static void cmdScrollHistoryTop (const char *cmdname, char *argstr) {
5609 scrollHistory(term->linecount);
5613 static void cmdScrollHistoryBottom (const char *cmdname, char *argstr) {
5614 scrollHistory(-term->linecount);
5618 static void cmdCommandMode (const char *cmdname, char *argstr) {
5619 if (term != NULL) {
5620 if (term->cmdMode == CMDMODE_NONE) tcmdlineinit(); else tcmdlinehide();
5625 // [show|hide]
5626 static void cmdCursor (const char *cmdname, char *argstr) {
5627 if (term != NULL) {
5628 char *s;
5630 if (iniParseArguments(argstr, "s!-", &s) != NULL) {
5631 tcmdlinemsgf("cursor is %s", ((term->c.state&CURSOR_HIDE) ? "hidden" : "visible"));
5632 } else {
5633 if (strcasecmp(s, "show") == 0) term->c.state &= ~CURSOR_HIDE;
5634 else if (strcasecmp(s, "hide") == 0) term->c.state |= CURSOR_HIDE;
5635 term->wantRedraw = 1;
5641 static void cmdResetAttrs (const char *cmdname, char *argstr) {
5642 if (term != NULL) {
5643 term->c.attr.attr &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
5644 term->c.attr.attr |= ATTR_DEFFG | ATTR_DEFBG;
5645 term->c.attr.fg = term->deffg;
5646 term->c.attr.bg = term->defbg;
5651 static void cmdResetCharset (const char *cmdname, char *argstr) {
5652 if (term != NULL) {
5653 term->mode &= ~(MODE_GFX0|MODE_GFX1);
5654 term->charset = MODE_GFX0;
5659 // [norm|alt]
5660 static void cmdScreen (const char *cmdname, char *argstr) {
5661 if (term != NULL) {
5662 char *s;
5664 if (iniParseArguments(argstr, "s!-", &s) != NULL) {
5665 tcmdlinemsgf("screen: %s", (IS_SET(MODE_ALTSCREEN) ? "alt" : "norm"));
5666 } else {
5667 if (strcasecmp(s, "norm") == 0) {
5668 if (IS_SET(MODE_ALTSCREEN)) tswapscreen();
5669 } else if (strcasecmp(s, "alt") == 0) {
5670 if (!IS_SET(MODE_ALTSCREEN)) tswapscreen();
5677 // [on|off]
5678 static void cmdMouseReports (const char *cmdname, char *argstr) {
5679 if (term != NULL) {
5680 int b;
5682 if (iniParseArguments(argstr, "b", &b) != NULL) {
5683 tcmdlinemsgf("mouse reports are o%s", (IS_SET(MODE_MOUSE) ? "n" : "ff"));
5684 } else {
5685 if (b) term->mode |= MODE_MOUSEBTN; else term->mode &= ~MODE_MOUSEBTN;
5691 static int cmd_parseIntArg (const char *fmt, char *argstr, int *b, int *global, int *toggle) {
5692 while (argstr[0]) {
5693 char *s = NULL;
5695 if (iniParseArguments(argstr, "R-", &argstr) != NULL) break;
5696 if (!argstr[0]) break;
5698 if (iniParseArguments(argstr, "s!-R-", &s, &argstr) != NULL) break;
5699 if (global && tolower(s[0]) == 'g') {
5700 *global = 1;
5701 } else if (toggle && tolower(s[0]) == 't') {
5702 *toggle = 1;
5703 } else {
5704 if (!b || iniParseArguments(s, fmt, b) != NULL) return -1;
5707 return 0;
5711 static void cmdUTF8Locale (const char *cmdname, char *argstr) {
5712 int b = -1, toggle = 0;
5714 if (cmd_parseIntArg("b", argstr, &b, NULL, &toggle) != 0) return;
5715 if (b == -1) {
5716 tcmdlinemsgf("UTF8Locale: %s", ((needConversion ? !term->needConv : 1) ? "yes" : "no"));
5717 } else {
5718 if (!needConversion) b = 1;
5719 if (term != NULL) term->needConv = !b;
5724 static void cmdAudibleBell (const char *cmdname, char *argstr) {
5725 int b = -1, toggle = 0, global = 0;
5727 if (term == NULL) return;
5728 if (cmd_parseIntArg("b", argstr, &b, &global, &toggle) != 0) return;
5730 if (b == -1) {
5731 tcmdlinemsgf("AudibleBell: %s", (global ? opt_audiblebell : (term->belltype&BELL_AUDIO)) ? "yes" : "no");
5732 } else {
5733 if (toggle) {
5734 if (global) opt_audiblebell = !opt_audiblebell; else term->belltype ^= BELL_AUDIO;
5735 } else {
5736 if (global) opt_audiblebell = b; else term->belltype = (term->belltype&~BELL_AUDIO)|(b!=0?BELL_AUDIO:0);
5742 static void cmdUrgentBell (const char *cmdname, char *argstr) {
5743 int b = -1, toggle = 0, global = 0;
5745 if (term == NULL) return;
5746 if (cmd_parseIntArg("b", argstr, &b, &global, &toggle) != 0) return;
5748 if (b == -1) {
5749 tcmdlinemsgf("UrgentBell: %s", (global ? opt_urgentbell : (term->belltype&BELL_URGENT)) ? "yes" : "no");
5750 } else {
5751 if (toggle) {
5752 if (global) opt_urgentbell = !opt_urgentbell; else term->belltype ^= BELL_URGENT;
5753 } else {
5754 if (global) opt_urgentbell = b; else term->belltype = (term->belltype&~BELL_URGENT)|(b!=0?BELL_URGENT:0);
5760 static void cmdMonochrome (const char *cmdname, char *argstr) {
5761 int b = -1, global = 0;
5763 if (term == NULL) return;
5764 if (cmd_parseIntArg("i{0,3}", argstr, &b, &global, NULL) != 0) return;
5766 if (b == -1) {
5767 b = (global ? globalBW : term->blackandwhite);
5768 tcmdlinemsgf("Monochrome: %d", b);
5769 } else {
5770 if (global) {
5771 if (b == 3) tcmdlinemsg("Monochrome-global can't be '3'!");
5772 else globalBW = b;
5773 } else {
5774 term->blackandwhite = b;
5777 tfulldirt();
5778 updateTabBar = 1;
5782 static void cmdCursorBlink (const char *cmdname, char *argstr) {
5783 int b = -1, global = 0;
5785 if (term == NULL) return;
5786 if (cmd_parseIntArg("i{0,10000}", argstr, &b, &global, NULL) != 0) return;
5788 if (b == -1) {
5789 b = (global ? opt_cursorBlink : term->curblink);
5790 tcmdlinemsgf("CursorBlink: %d", b);
5791 } else {
5792 if (global) {
5793 opt_cursorBlink = b;
5794 } else {
5795 term->curblink = b;
5796 term->curbhidden = 0;
5799 tfulldirt();
5800 updateTabBar = 1;
5804 static void cmdCursorBlinkInactive (const char *cmdname, char *argstr) {
5805 int b = -1, global = 0, toggle = 0, *iptr;
5807 if (term == NULL) return;
5808 if (cmd_parseIntArg("b", argstr, &b, &global, &toggle) != 0) return;
5810 iptr = (global ? &opt_cursorBlinkInactive : &term->curblinkinactive);
5811 if (b != -1) {
5812 if (toggle) *iptr = !(*iptr); else *iptr = b;
5813 draw(0);
5818 static void cmdIgnoreClose (const char *cmdname, char *argstr) {
5819 int b = -666;
5821 if (term == NULL) return;
5822 if (cmd_parseIntArg("i{-1,1}", argstr, &b, NULL, NULL) != 0) return;
5824 if (b == -666) {
5825 tcmdlinemsgf("IgnoreClose: %d", opt_ignoreclose);
5826 } else {
5827 opt_ignoreclose = b;
5832 static void cmdMaxHistory (const char *cmdname, char *argstr) {
5833 int b = -1, global = 0;
5835 if (term == NULL) return;
5836 if (cmd_parseIntArg("i{0,65535}", argstr, &b, &global, NULL) != 0) return;
5838 if (b == -1) {
5839 tcmdlinemsgf("MaxHistory: %d", (global?opt_maxhistory:term->maxhistory));
5840 } else {
5841 if (!global) tadjustmaxhistory(b); else opt_maxhistory = b;
5846 static void cmdMaxDrawTimeout (const char *cmdname, char *argstr) {
5847 int b = -1;
5849 if (term == NULL) return;
5850 if (cmd_parseIntArg("i{100,60000}", argstr, &b, NULL, NULL) != 0) return;
5852 if (b == -1) {
5853 tcmdlinemsgf("MaxDrawTimeout: %d", opt_maxdrawtimeout);
5854 } else {
5855 opt_maxdrawtimeout = b;
5860 static void cmdTabPosition (const char *cmdname, char *argstr) {
5861 int newpos = -1;
5863 while (argstr[0]) {
5864 char *s = NULL;
5866 if (iniParseArguments(argstr, "R-", &argstr) != NULL) break;
5867 if (!argstr[0]) break;
5869 if (iniParseArguments(argstr, "s!-R-", &s, &argstr) != NULL) break;
5870 if (tolower(s[0]) == 't') newpos = 1;
5871 else if (tolower(s[0]) == 'b') newpos = 0;
5872 else if (iniParseArguments(s, "i{0,1}", &newpos) != NULL) return;
5875 if (newpos == -1) {
5876 tcmdlinemsgf("TabPostion: %s", (opt_tabposition == 0 ? "bottom" : "top"));
5877 } else if (opt_tabposition != newpos) {
5878 opt_tabposition = newpos;
5879 updateTabBar = 1;
5880 tfulldirt();
5881 draw(1);
5882 xclearunused();
5887 static void cmdTabCount (const char *cmdname, char *argstr) {
5888 int b = -1;
5890 if (term == NULL) return;
5891 if (cmd_parseIntArg("i{1,128}", argstr, &b, NULL, NULL) != 0) return;
5893 if (b == -1) {
5894 tcmdlinemsgf("TabCount: %d", opt_tabcount);
5895 } else if (opt_tabcount != b) {
5896 opt_tabcount = b;
5897 fixFirstTab();
5898 xdrawTabBar();
5903 static void cmdDoFullRedraw (const char *cmdname, char *argstr) {
5904 updateTabBar = 1;
5905 tfulldirt();
5906 xclearunused();
5907 draw(1);
5911 static void cmdTitle (const char *cmdname, char *argstr) {
5912 if (term != NULL) {
5913 char *s = NULL;
5915 if (iniParseArguments(argstr, "s-", &s) != NULL || s == NULL) return;
5916 memset(term->title, 0, sizeof(term->title));
5917 while (strlen(s) > ESC_TITLE_SIZ) utf8choplast(s);
5918 fprintf(stderr, "[%s]\n", s);
5919 strncpy(term->title, s, ESC_TITLE_SIZ);
5920 fixWindowTitle(term);
5921 updateTabBar = 1;
5922 xdrawTabBar();
5927 static void cmdFastRedraw (const char *cmdname, char *argstr) {
5928 int b = -1, global = 0, toggle = 0, *iptr;
5930 if (term == NULL) return;
5931 if (cmd_parseIntArg("b", argstr, &b, &global, &toggle) != 0) return;
5933 iptr = (global ? &opt_fastredraw : &term->fastredraw);
5934 if (b != -1) {
5935 if (toggle) *iptr = !(*iptr); else *iptr = b;
5936 draw(0);
5937 } else {
5938 tcmdlinemsgf("FastRedraw: %s", (*iptr ? "yes" : "no"));
5943 static const Command commandList[] = {
5944 {"PastePrimary", cmdPastePrimary},
5945 {"PasteSecondary", cmdPasteSecondary},
5946 {"PasteClipboard", cmdPasteClipboard},
5947 {"exec", cmdExec},
5948 {"NewTab", cmdNewTab}, // 'noswitch' 'next' 'prev' 'first' 'last'
5949 {"CloseTab", cmdCloseTab},
5950 {"KillTab", cmdKillTab},
5951 {"SwitchToTab", cmdSwitchToTab}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5952 {"MoveTabTo", cmdMoveTabTo}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5953 {"DefaultFG", cmdDefaultFG},
5954 {"DefaultBG", cmdDefaultBG},
5955 {"ScrollHistoryLineUp", cmdScrollHistoryLineUp},
5956 {"ScrollHistoryPageUp", cmdScrollHistoryPageUp},
5957 {"ScrollHistoryLineDown", cmdScrollHistoryLineDown},
5958 {"ScrollHistoryPageDown", cmdScrollHistoryPageDown},
5959 {"ScrollHistoryTop", cmdScrollHistoryTop},
5960 {"ScrollHistoryBottom", cmdScrollHistoryBottom},
5961 {"UTF8Locale", cmdUTF8Locale}, // 'on', 'off'
5962 {"AudibleBell", cmdAudibleBell},
5963 {"UrgentBell", cmdUrgentBell},
5964 {"CommandMode", cmdCommandMode},
5965 {"Cursor", cmdCursor},
5966 {"ResetAttrs", cmdResetAttrs},
5967 {"ResetCharset", cmdResetCharset},
5968 {"Screen", cmdScreen},
5969 {"MouseReports", cmdMouseReports},
5970 {"Monochrome", cmdMonochrome},
5971 {"Mono", cmdMonochrome},
5972 {"CursorBlink", cmdCursorBlink},
5973 {"CursorBlinkInactive", cmdCursorBlinkInactive},
5974 {"IgnoreClose", cmdIgnoreClose},
5975 {"MaxHistory", cmdMaxHistory},
5976 {"MaxDrawTimeout", cmdMaxDrawTimeout},
5977 {"TabPosition", cmdTabPosition},
5978 {"TabCount", cmdTabCount},
5979 {"DoFullRedraw", cmdDoFullRedraw},
5980 {"Title", cmdTitle},
5981 {"FastRedraw", cmdFastRedraw},
5983 {"KeyBind", NULL},
5984 {"KeyTrans", NULL},
5985 {"KeyMap", NULL},
5986 {"UniMap", NULL},
5988 {"term", cmdTermName},
5989 {"title", cmdWinTitle},
5990 {"tabsize", cmdTabSize},
5991 {"defaultcursorfg", cmdDefaultCursorFG},
5992 {"defaultcursorbg", cmdDefaultCursorBG},
5993 {"defaultinactivecursorfg", cmdDefaultInactiveCursorFG},
5994 {"defaultinactivecursorbg", cmdDefaultInactiveCursorBG},
5995 {"defaultboldfg", cmdDefaultBoldFG},
5996 {"defaultunderlinefg", cmdDefaultUnderlineFG},
5998 {NULL, NULL}
6002 static const char *findCommandCompletion (const char *str, int slen, const char *prev) {
6003 const char *res = NULL;
6004 int phit = 0;
6006 if (slen < 1) return NULL;
6007 for (int f = 0; commandList[f].name != NULL; ++f) {
6008 if (strlen(commandList[f].name) >= slen && strncasecmp(commandList[f].name, str, slen) == 0) {
6009 if (prev == NULL || phit) return commandList[f].name;
6010 if (strcasecmp(commandList[f].name, prev) == 0) phit = 1;
6011 if (res == NULL) res = commandList[f].name;
6014 return res;
6018 // !0: NewTab command
6019 static int executeCommand (const char *str, int slen) {
6020 const char *e;
6021 char *cmdname;
6022 int cmdfound = 0;
6024 if (str == NULL) return 0;
6025 if (slen < 0) slen = strlen(str);
6027 for (int f = 0; f < slen; ++f) if (!str[f]) { slen = f; break; }
6029 while (slen > 0 && isspace(*str)) { ++str; --slen; }
6030 if (slen < 1 || !str[0]) return 0;
6032 for (e = str; slen > 0 && !isspace(*e); ++e, --slen) ;
6034 if (e-str > 127) return 0;
6035 cmdname = alloca(e-str+8);
6036 if (cmdname == NULL) return 0;
6037 memcpy(cmdname, str, e-str);
6038 cmdname[e-str] = 0;
6039 if (opt_disabletabs && strcasecmp(cmdname, "NewTab") == 0) return 1;
6041 while (slen > 0 && isspace(*e)) { ++e; --slen; }
6042 //FIXME: ugly copypaste!
6044 for (int f = 0; commandList[f].name != NULL; ++f) {
6045 if (strcasecmp(commandList[f].name, cmdname) == 0 && commandList[f].fn != NULL) {
6046 char *left = calloc(slen+2, 1);
6048 if (left != NULL) {
6049 if (slen > 0) memcpy(left, e, slen);
6050 //fprintf(stderr, "command: [%s]; args: [%s]\n", cmdname, left);
6051 commandList[f].fn(cmdname, left);
6052 free(left);
6054 cmdfound = 1;
6055 break;
6059 if (!cmdfound) {
6060 char *left = calloc(slen+2, 1);
6062 if (left != NULL) {
6063 if (slen > 0) memcpy(left, e, slen);
6064 processMiscCmds(cmdname, left);
6065 free(left);
6069 return 0;
6074 static const char *cmdpSkipStr (const char *str) {
6075 while (*str && isspace(*str)) ++str;
6076 if (str[0] && str[0] != ';') {
6077 char qch = ' ';
6079 while (*str) {
6080 if (*str == ';' && qch == ' ') break;
6081 if (qch != ' ' && *str == qch) { qch = ' '; ++str; continue; }
6082 if (*str == '"' || *str == '\'') {
6083 if (qch == ' ') qch = *str;
6084 ++str;
6085 continue;
6087 if (*str++ == '\\' && *str) ++str;
6090 return str;
6095 static void executeCommands (const char *str) {
6096 oldTerm = term;
6097 oldTermIdx = termidx;
6098 newTerm = NULL;
6099 newTermSwitch = 0;
6100 if (str == NULL) return;
6101 while (*str) {
6102 const char *ce;
6103 char qch;
6105 while (*str && isspace(*str)) ++str;
6106 if (!*str) break;
6107 if (*str == ';') { ++str; continue; }
6109 ce = str;
6110 qch = ' ';
6111 while (*ce) {
6112 if (*ce == ';' && qch == ' ') break;
6113 if (qch != ' ' && *ce == qch) { qch = ' '; ++ce; continue; }
6114 if (*ce == '"' || *ce == '\'') {
6115 if (qch == ' ') qch = *ce;
6116 ++ce;
6117 continue;
6119 if (*ce++ == '\\' && *ce) ++ce;
6122 if (executeCommand(str, ce-str)) break;
6123 if (*ce) str = ce+1; else break;
6125 flushNewTerm();
6126 switchToTerm(oldTermIdx, 1);
6130 ////////////////////////////////////////////////////////////////////////////////
6131 int main (int argc, char *argv[]) {
6132 char *configfile = NULL;
6133 char *runcmd = NULL;
6135 //dbgLogInit();
6137 for (int f = 1; f < argc; f++) {
6138 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
6139 if (strcmp(argv[f], "-into") == 0) { ++f; continue; }
6140 if (strcmp(argv[f], "-embed") == 0) { ++f; continue; }
6141 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
6142 case 'e': f = argc+1; break;
6143 case 't':
6144 case 'c':
6145 case 'w':
6146 case 'b':
6147 case 'R':
6148 ++f;
6149 break;
6150 case 'T':
6151 if (++f < argc) {
6152 free(opt_term);
6153 opt_term = strdup(argv[f]);
6154 opt_term_locked = 1;
6156 break;
6157 case 'C':
6158 if (++f < argc) {
6159 if (configfile != NULL) free(configfile);
6160 configfile = strdup(argv[f]);
6162 break;
6163 case 'l':
6164 if (++f < argc) cliLocale = argv[f];
6165 break;
6166 case 'S': // single-tab mode
6167 opt_disabletabs = 1;
6168 break;
6169 case 'v':
6170 case 'h':
6171 default:
6172 fprintf(stderr, "%s", USAGE);
6173 exit(EXIT_FAILURE);
6177 initDefaultOptions();
6178 if (configfile == NULL) {
6179 const char *home = getenv("HOME");
6181 if (home != NULL) {
6182 configfile = SPrintf("%s/.sterm.rc", home);
6183 if (loadConfig(configfile) == 0) goto cfgdone;
6184 free(configfile); configfile = NULL;
6186 configfile = SPrintf("%s/.config/sterm.rc", home);
6187 if (loadConfig(configfile) == 0) goto cfgdone;
6188 free(configfile); configfile = NULL;
6191 configfile = SPrintf("/etc/sterm.rc");
6192 if (loadConfig(configfile) == 0) goto cfgdone;
6193 free(configfile); configfile = NULL;
6195 configfile = SPrintf("/etc/sterm/sterm.rc");
6196 if (loadConfig(configfile) == 0) goto cfgdone;
6197 free(configfile); configfile = NULL;
6199 configfile = SPrintf("./.sterm.rc");
6200 if (loadConfig(configfile) == 0) goto cfgdone;
6201 free(configfile); configfile = NULL;
6202 // no config
6203 } else {
6204 if (loadConfig(configfile) < 0) die("config file '%s' not found!", configfile);
6206 cfgdone:
6207 if (configfile != NULL) free(configfile); configfile = NULL;
6209 for (int f = 1; f < argc; f++) {
6210 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
6211 if (strcmp(argv[f], "-into") == 0 || strcmp(argv[f], "-embed") == 0) {
6212 if (opt_embed) free(opt_embed);
6213 opt_embed = strdup(argv[f]);
6214 continue;
6216 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
6217 case 't':
6218 if (++f < argc) {
6219 free(opt_title);
6220 opt_title = strdup(argv[f]);
6222 break;
6223 case 'c':
6224 if (++f < argc) {
6225 free(opt_class);
6226 opt_class = strdup(argv[f]);
6228 break;
6229 case 'w':
6230 if (++f < argc) {
6231 if (opt_embed) free(opt_embed);
6232 opt_embed = strdup(argv[f]);
6234 break;
6235 case 'R':
6236 if (++f < argc) runcmd = argv[f];
6237 break;
6238 case 'e':
6239 /* eat every remaining arguments */
6240 if (++f < argc) opt_cmd = &argv[f];
6241 f = argc+1;
6242 case 'T':
6243 case 'C':
6244 case 'l':
6245 ++f;
6246 break;
6247 case 'S':
6248 break;
6249 case 'v':
6250 case 'h':
6251 default:
6252 fprintf(stderr, "%s", USAGE);
6253 exit(EXIT_FAILURE);
6257 setenv("TERM", opt_term, 1);
6258 mclock_init();
6259 setlocale(LC_ALL, "");
6260 initLCConversion();
6261 updateTabBar = 1;
6262 termidx = 0;
6263 term = termalloc();
6264 if (term->execcmd != NULL) { free(term->execcmd); term->execcmd = NULL; }
6265 tinitialize(80, 25);
6266 if (ttynew(term) != 0) die("can't run process");
6267 opt_cmd = NULL;
6268 xinit();
6269 selinit();
6270 if (runcmd != NULL) executeCommands(runcmd);
6271 run();
6272 return 0;