'mono 3' will override global mono setting for tab
[k8sterm.git] / src / sterm.c
blobbe89c1b9682515dbdbe4dff0d91f3223c07bc235
1 /* See LICENSE for licence details. */
2 #define VERSION "0.3.1.beta3"
4 #ifdef _XOPEN_SOURCE
5 # undef _XOPEN_SOURCE
6 #endif
7 #define _XOPEN_SOURCE 600
9 #include <alloca.h>
10 #include <ctype.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <iconv.h>
14 #include <limits.h>
15 #include <locale.h>
16 #include <pty.h>
17 #include <stdarg.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <strings.h>
22 #include <signal.h>
23 #include <sys/ioctl.h>
24 #include <sys/select.h>
25 #include <sys/stat.h>
26 #include <sys/time.h>
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #include <time.h>
30 #include <unistd.h>
31 #include <X11/Xatom.h>
32 #include <X11/Xlib.h>
33 #include <X11/Xutil.h>
34 #include <X11/cursorfont.h>
35 #include <X11/keysym.h>
37 //#include "dbglog.h"
40 // uncomment the following to use XCreateGlyphCursor() instead of XCreatePixmapCursor()
41 //#define BLANKPTR_USE_GLYPH_CURSOR
44 //#define PASTE_SELECTION_DEBUG
46 //#define DUMP_KEYSYMS
48 //#define KEYPAD_DUMP
50 //#define DUMP_PROG_OUTPUT
51 //#define DUMP_PROG_INPUT
53 //#define DUMP_IO_READ
54 //#define DUMP_IO_WRITE
56 #if defined(DUMP_IO_READ) || defined(DUMP_IO_WRITE)
57 # define DUMP_IO
58 #endif
60 #ifdef KEYPAD_DUMP
61 # define DUMP_KEYPAD_SWITCH(strflag,strstate) do { fprintf(stderr, "KEYPAD %s (%s)\n", (strstate), (strflag)); } while (0)
62 #else
63 # define DUMP_KEYPAD_SWITCH(strflag,strstate) ((void)(sizeof(strflag)+sizeof(strstate)))
64 #endif
67 ////////////////////////////////////////////////////////////////////////////////
68 #define USAGE \
69 "sterm " VERSION " (c) 2010-2012 st engineers and Ketmar // Vampire Avalon\n" \
70 "usage: sterm [-t title] [-c class] [-w windowid] [-T termname] [-C config_file] [-l langiconv] [-S] [-v] [-R stcmd] [-e command...]\n"
73 ////////////////////////////////////////////////////////////////////////////////
74 #define FONT "-*-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"
75 #define FONTBOLD "-*-fixed-bold-r-normal-*-18-*-*-*-*-*-*-*"
76 #define FONTTAB "-*-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*"
79 /* Default shell to use if SHELL is not set in the env */
80 #define SHELL "/bin/sh"
83 /* Terminal colors (16 first used in escape sequence) */
84 static const char *defcolornames[] = {
85 #if 1
86 /* 8 normal colors */
87 "black",
88 "red3",
89 "green3",
90 "yellow3",
91 "blue2",
92 "magenta3",
93 "cyan3",
94 "gray90",
95 /* 8 bright colors */
96 "gray50",
97 "red",
98 "green",
99 "yellow",
100 "#5c5cff",
101 "magenta",
102 "cyan",
103 "white",
104 #else
105 /* 8 normal colors */
106 "#000000",
107 "#b21818",
108 "#18b218",
109 "#b26818",
110 "#1818b2",
111 "#b218b2",
112 "#18b2b2",
113 "#b2b2b2",
114 /* 8 bright colors */
115 "#686868",
116 "#ff5454",
117 "#54ff54",
118 "#ffff54",
119 "#5454ff",
120 "#ff54ff",
121 "#54ffff",
122 "#ffffff",
123 #endif
127 /* more colors can be added after 255 to use with DefaultXX */
128 static const char *defextcolornames[] = {
129 "#cccccc", /* 256 */
130 "#333333", /* 257 */
131 /* root terminal fg and bg */
132 "#809a70", /* 258 */
133 "#002000", /* 259 */
134 /* bold and underline */
135 "#00afaf", /* 260 */
136 "#00af00", /* 261 */
140 /* Default colors (colorname index) foreground, background, cursor, unfocused cursor */
141 #define DEFAULT_FG (7)
142 #define DEFAULT_BG (0)
143 #define DEFAULT_CS (256)
144 #define DEFAULT_UCS (257)
146 #define TNAME "xterm"
148 /* double-click timeout (in milliseconds) between clicks for selection */
149 #define DOUBLECLICK_TIMEOUT (300)
150 #define TRIPLECLICK_TIMEOUT (2*DOUBLECLICK_TIMEOUT)
151 //#define SELECT_TIMEOUT 20 /* 20 ms */
152 #define DRAW_TIMEOUT 18 /* 18 ms */
154 #define TAB (8)
157 ////////////////////////////////////////////////////////////////////////////////
158 #define MAX_COLOR (511)
160 /* XEMBED messages */
161 #define XEMBED_FOCUS_IN (4)
162 #define XEMBED_FOCUS_OUT (5)
165 /* Arbitrary sizes */
166 #define ESC_TITLE_SIZ (256)
167 #define ESC_BUF_SIZ (256)
168 #define ESC_ARG_SIZ (16)
169 #define DRAW_BUF_SIZ (2048)
170 #define UTF_SIZ (4)
171 #define OBUFSIZ (256)
172 #define WBUFSIZ (256)
175 /* masks for key translation */
176 #define XK_NO_MOD (UINT_MAX)
177 #define XK_ANY_MOD (0)
180 /* misc utility macros */
181 //#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000+(t1.tv_usec-t2.tv_usec)/1000)
182 #define SERRNO strerror(errno)
183 #define MIN(a, b) ((a) < (b) ? (a) : (b))
184 #define MAX(a, b) ((a) < (b) ? (b) : (a))
185 #define LEN(a) (sizeof(a)/sizeof(a[0]))
186 #define DEFAULT(a, b) (a) = ((a) ? (a) : (b))
187 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
188 #define LIMIT(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x))
189 #define ATTRCMP(a, b) ((a).attr != (b).attr || (a).fg != (b).fg || (a).bg != (b).bg)
190 #define IS_SET(flag) (term->mode&(flag))
191 #define X2COL(x) ((x)/xw.cw)
192 #define Y2ROW(y) ((y-(opt_tabposition==1 ? xw.tabheight : 0))/xw.ch-(term!=NULL ? term->topline : 0))
193 //#define IS_GFX(mode) (((mode)&(ATTR_GFX|ATTR_G1)) == ATTR_GFX || ((mode)&(ATTR_GFX1|ATTR_G1)) == (ATTR_GFX1|ATTR_G1))
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 unsigned long ulong;
284 typedef unsigned short ushort;
287 typedef struct __attribute__((packed)) {
288 char c[UTF_SIZ]; /* character code */
289 uchar attr; /* attribute flags */
290 ushort fg; /* foreground */
291 ushort bg; /* background */
292 uchar state; /* state flags */
293 } Glyph;
296 typedef Glyph *Line;
298 typedef struct {
299 Glyph attr; /* current char attributes */
300 int x;
301 int y;
302 char state;
303 } TCursor;
306 /* CSI Escape sequence structs */
307 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
308 typedef struct {
309 char buf[ESC_BUF_SIZ]; /* raw string */
310 int len; /* raw string length */
311 char priv;
312 int arg[ESC_ARG_SIZ];
313 int narg; /* nb of args */
314 char mode;
315 } CSIEscape;
318 /* Purely graphic info */
319 typedef struct {
320 Display *dpy;
321 Colormap cmap;
322 Window win;
323 Cursor cursor;
324 Cursor blankPtr;
325 Atom xembed;
326 XIM xim;
327 XIC xic;
328 int scr;
329 int w; /* window width */
330 int h; /* window height */
331 int bufw; /* pixmap width */
332 int bufh; /* pixmap height */
333 int ch; /* char height */
334 int cw; /* char width */
335 char state; /* focus, redraw, visible */
337 int tch; /* tab text char height */
338 Pixmap pictab;
339 int tabheight;
340 //struct timeval lastdraw;
341 } XWindow;
344 /* TODO: use better name for vars... */
345 typedef struct {
346 int mode;
347 int bx, by;
348 int ex, ey;
349 struct { int x, y; } b, e;
350 char *clip;
351 Atom xtarget;
352 int tclick1;
353 int tclick2;
354 } Selection;
357 /* Drawing Context */
358 typedef struct {
359 ulong *ncol; // normal colors
360 ulong *bcol; // b/w colors
361 ulong *gcol; // green colors
362 ulong *clrs[3];
363 GC gc;
364 struct {
365 int ascent;
366 int descent;
367 short lbearing;
368 short rbearing;
369 XFontSet set;
370 Font fid;
371 } font[3];
372 } DC;
375 typedef void (*CmdLineExecFn) (int cancelled);
378 #define CMDLINE_SIZE (256)
380 /* Internal representation of the screen */
381 typedef struct Term {
382 int cmdfd;
383 int dead;
384 int exitcode;
385 int needConv; /* 0: utf-8 locale */
386 int belltype;
387 int blackandwhite;
389 int curblink;
390 int curbhidden;
391 int lastBlinkTime;
392 int curblinkinactive;
394 int row; /* nb row */
395 int col; /* nb col */
396 int topline; /* top line for drawing (0: no history; 1: show one history line; etc) */
397 int linecount; /* full, with history */
398 int maxhistory;/* max history lines; 0: none; <0: infinite */
399 Line *line; /* screen */
400 Line *alt; /* alternate screen */
401 char *dirty; /* dirtyness of lines */
402 TCursor c; /* cursor */
403 int top; /* top scroll limit */
404 int bot; /* bottom scroll limit */
405 int mode; /* terminal mode flags */
406 int mousemode; /* mouse mode: 1000, 1005, 1006, 1015 */
407 int esc; /* escape state flags */
408 int charset; /* 0 or 1 */
410 TCursor csaved; /* saved cursor info */
411 // old cursor position
412 int oldcx;
413 int oldcy;
415 char title[ESC_TITLE_SIZ+1];
416 int titlelen;
418 int mouseob;
419 int mouseox;
420 int mouseoy;
422 char obuf[OBUFSIZ];
423 #ifdef DUMP_PROG_OUTPUT
424 int xobuflen;
425 #endif
426 int obuflen;
428 char ubuf[UTF_SIZ];
429 int ubufpos;
431 char drawbuf[DRAW_BUF_SIZ];
433 char wrbuf[WBUFSIZ];
434 int wrbufsize;
435 int wrbufused;
436 int wrbufpos;
438 CSIEscape escseq;
439 Selection sel;
440 pid_t pid;
441 int lastDrawTime;
443 char *execcmd;
445 Pixmap picbuf;
446 int picbufw;
447 int picbufh;
449 ushort deffg;
450 ushort defbg;
452 int wantRedraw;
454 int lastActiveTime;
456 int cmdMode;
457 char cmdline[UTF_SIZ*CMDLINE_SIZE];
458 int cmdreslen; // byte length of 'reserved' (read-only) part
459 int cmdofs;
460 char cmdc[UTF_SIZ+1];
461 int cmdcl;
462 int cmdtabpos;
463 const char *cmdprevc;
464 CmdLineExecFn cmdexecfn;
465 } Term;
468 ////////////////////////////////////////////////////////////////////////////////
469 /* Globals */
470 static ushort *unimap = NULL; // 0 or 65535 items; 127: special; high bit set: use alt(gfx) mode; 0: empty
472 static char **opt_cmd = NULL;
473 static char *opt_title = NULL;
474 static char *opt_embed = NULL;
475 static char *opt_class = NULL;
476 static char *opt_term = NULL;
477 static char *opt_fontnorm = NULL;
478 static char *opt_fontbold = NULL;
479 static char *opt_fonttab = NULL;
480 static char *opt_shell = NULL;
481 static char *opt_colornames[512];
482 static int opt_term_locked = 0;
483 static int opt_doubleclick_timeout = DOUBLECLICK_TIMEOUT;
484 static int opt_tripleclick_timeout = TRIPLECLICK_TIMEOUT;
485 static int opt_tabsize = TAB;
486 static int defaultFG = DEFAULT_FG;
487 static int defaultBG = DEFAULT_BG;
488 static int defaultCursorFG = 0;
489 static int defaultCursorBG = DEFAULT_CS;
490 static int defaultCursorInactiveFG = 0;
491 static int defaultCursorInactiveBG = DEFAULT_UCS;
492 static int defaultBoldFG = -1;
493 static int defaultUnderlineFG = -1;
494 static int normalTabFG = 258;
495 static int normalTabBG = 257;
496 static int activeTabFG = 258;
497 static int activeTabBG = 0;
498 static int opt_maxhistory = 512;
499 static int opt_ptrblank = 2000; // delay; 0: never
500 static int opt_tabcount = 6;
501 static int opt_tabposition = 0;
502 static int opt_drawtimeout = DRAW_TIMEOUT;
503 static int opt_disabletabs = 0;
504 static int opt_audiblebell = 1;
505 static int opt_urgentbell = 1;
506 static int opt_cursorBlink = 0;
507 static int opt_cursorBlinkInactive = 0;
508 static int opt_drawunderline = 1;
509 static int opt_ignoreclose = 0;
510 static int ptrBlanked = 0;
511 static int ptrLastMove = 0;
512 static int globalBW = 0;
514 static Term **term_array = NULL;
515 static int term_count = 0;
516 static int term_array_size = 0;
517 static Term *term; // current terminal
518 static int termidx; // current terminal index; DON'T RELAY ON IT!
519 static int updateTabBar;
520 static int lastDrawTime = 0;
521 static char *lastSelStr = NULL;
522 //static int lastSelLength = 0;
524 static int exitcode = 0;
525 static int closeRequestComes = 0;
527 static DC dc;
528 static XWindow xw;
530 static Atom XA_VT_SELECTION;
531 static Atom XA_CLIPBOARD;
532 static Atom XA_UTF8;
533 static Atom XA_TARGETS;
534 static Atom XA_NETWM_NAME;
535 static Atom XA_WM_DELETE_WINDOW;
538 ////////////////////////////////////////////////////////////////////////////////
539 typedef struct {
540 KeySym src;
541 KeySym dst;
542 } KeyTransDef;
545 static KeyTransDef *keytrans = NULL;
546 static int keytrans_size = 0;
547 static int keytrans_used = 0;
550 typedef struct {
551 KeySym key;
552 uint mask;
553 int kp;
554 char *str;
555 } KeyInfoDef;
558 static KeyInfoDef *keybinds = NULL;
559 static int keybinds_size = 0;
560 static int keybinds_used = 0;
562 static KeyInfoDef *keymap = NULL;
563 static int keymap_size = 0;
564 static int keymap_used = 0;
567 ////////////////////////////////////////////////////////////////////////////////
568 static void executeCommands (const char *str);
569 static const char *findCommandCompletion (const char *str, int slen, const char *prev);
571 static void ttyresize (void);
572 static void tputc (const char *c); // `c` is utf-8
573 static void ttywrite (const char *s, size_t n);
574 static void tsetdirt (int top, int bot);
575 static void tfulldirt (void);
577 static void xseturgency (int add);
578 static void xfixsel (void);
579 static void xblankPointer (void);
580 static void xunblankPointer (void);
582 static void draw (int forced);
584 static void tcmdput (const char *s, int len);
587 static inline void ttywritestr (const char *s) { if (s != NULL && s[0]) ttywrite(s, strlen(s)); }
590 static inline ulong getColor (int idx) {
591 if (globalBW && (term == NULL || !term->blackandwhite)) return dc.clrs[globalBW][idx];
592 if (term != NULL) return dc.clrs[term->blackandwhite%3][idx];
593 return dc.clrs[0][idx];
595 if (globalBW) return dc.bcol[idx];
596 if (term != NULL) {
597 return (term->blackandwhite ? dc.bcol[idx] : dc.ncol[idx]);
599 return dc.ncol[idx];
604 ////////////////////////////////////////////////////////////////////////////////
606 static void trimstr (char *s) {
607 char *e;
609 while (*s && isspace(*s)) ++s;
610 for (e = s+strlen(s); e > s; --e) if (!isspace(e[-1])) break;
611 if (e <= s) *s = 0; else *e = 0;
615 // parse the argument list
616 // return error message or NULL
617 // format:
618 // '*': skip
619 // 's': string (char *)
620 // 'i': integer (int *)
621 // 'b': boolean (int *)
622 // '|': optional arguments follows
623 // '.': stop parsing, ignore rest
624 // 'R': stop parsing, set rest ptr (char *)
625 // string modifiers (also for 'R'):
626 // '!' -- don't allow empty strings
627 // '-' -- trim spaces
628 // int modifiers:
629 // {lo,hi}
630 // {,hi}
631 // {lo}
632 // WARNING! `line` will be modified!
633 // WARNING! string pointers will point INSIDE `line`, so don't discard it!
634 // UGLY! REWRITE!
635 const char *iniParseArguments (char *line, const char *fmt, ...) {
636 va_list ap;
637 int inOptional = 0;
639 if (line == NULL) return "alas";
640 trimstr(line);
641 va_start(ap, fmt);
642 while (*fmt) {
643 char spec = *fmt++, *args;
645 if (spec == '|') { inOptional = 1; continue; }
646 if (spec == '.') { va_end(ap); return NULL; }
648 while (*line && isspace(*line)) ++line;
649 if (*line == '#') *line = 0;
651 if (spec == 'R') {
652 char **p = va_arg(ap, char **);
653 int noempty = 0;
655 while (*fmt) {
656 if (*fmt == '!') { ++fmt; noempty = 1; }
657 else if (*fmt == '-') { ++fmt; trimstr(line); }
658 else break;
660 va_end(ap);
661 if (noempty && !line[0]) return "invalid empty arg";
662 if (p != NULL) *p = line;
663 return NULL;
666 if (!line[0]) {
667 // end of line, stop right here
668 va_end(ap);
669 if (!inOptional) return "out of args";
670 return NULL;
673 args = line;
675 char *dest = args, qch = '#';
676 int n;
678 if (line[0] == '"' || line[0] == '\'') qch = *line++;
680 while (*line && *line != qch) {
681 if (qch == '#' && isspace(*line)) break;
683 if (*line == '\\') {
684 if (!line[1]) { va_end(ap); return "invalid escape"; }
685 switch (*(++line)) {
686 case 'n': *dest++ = '\n'; ++line; break;
687 case 'r': *dest++ = '\r'; ++line; break;
688 case 't': *dest++ = '\t'; ++line; break;
689 case 'a': *dest++ = '\a'; ++line; break;
690 case 'e': *dest++ = '\x1b'; ++line; break; // esc
691 case 's': *dest++ = ' '; ++line; break;
692 case 'x': // hex
693 ++line;
694 if (!isxdigit(*line)) { va_end(ap); return "invalid hex escape"; }
695 n = toupper(*line)-'0'; if (n > 9) n -= 7;
696 ++line;
697 if (isxdigit(*line)) {
698 int b = toupper(*line)-'0'; if (b > 9) b -= 7;
700 n = (n*16)+b;
701 ++line;
703 *dest++ = n;
704 break;
705 case '0': // octal
706 n = 0;
707 for (int f = 0; f < 4; ++f) {
708 if (*line < '0' || *line > '7') break;
709 n = (n*8)+(line[0]-'0');
710 if (n > 255) { va_end(ap); return "invalid oct escape"; }
711 ++line;
713 if (n == 0) { va_end(ap); return "invalid oct escape"; }
714 *dest++ = n;
715 break;
716 case '1'...'9': // decimal
717 n = 0;
718 for (int f = 0; f < 3; ++f) {
719 if (*line < '0' || *line > '9') break;
720 n = (n*8)+(line[0]-'0');
721 if (n > 255) { va_end(ap); return "invalid dec escape"; }
722 ++line;
724 if (n == 0) { va_end(ap); return "invalid oct escape"; }
725 *dest++ = n;
726 break;
727 default:
728 *dest++ = *line++;
729 break;
731 } else {
732 *dest++ = *line++;
735 if (qch != '#') {
736 if (*line != qch) return "unfinished string";
737 if (*line) ++line;
738 } else if (*line && *line != '#') ++line;
739 *dest = 0;
741 // now process and convert argument
742 switch (spec) {
743 case '*': /* skip */
744 break;
745 case 's': { /* string */
746 int noempty = 0, trim = 0;
747 char **p;
749 for (;;) {
750 if (*fmt == '!') { noempty = 1; ++fmt; }
751 else if (*fmt == '-') { trim = 1; ++fmt; }
752 else break;
755 if (trim) trimstr(args);
757 if (noempty && !args[0]) { va_end(ap); return "invalid empty string"; }
758 p = va_arg(ap, char **);
759 if (p != NULL) *p = args;
760 } break;
761 case 'i': /* int */
762 if (!args[0]) {
763 va_end(ap);
764 return "invalid integer";
765 } else {
766 int *p = va_arg(ap, int *);
767 long int n;
768 char *eptr;
770 trimstr(args);
771 n = strtol(args, &eptr, 0);
772 if (*eptr) { va_end(ap); return "invalid integer"; }
774 if (*fmt == '{') {
775 // check min/max
776 int minmax[2], haveminmax[2];
778 haveminmax[0] = haveminmax[1] = 0;
779 minmax[0] = minmax[1] = 0;
780 ++fmt;
781 for (int f = 0; f < 2; ++f) {
782 if (isdigit(*fmt) || *fmt == '-' || *fmt == '+') {
783 int neg = 0;
784 haveminmax[f] = 1;
785 if (*fmt == '-') neg = 1;
786 if (!isdigit(*fmt)) {
787 ++fmt;
788 if (!isdigit(*fmt)) { va_end(ap); return "invalid integer bounds"; }
790 while (isdigit(*fmt)) {
791 minmax[f] = minmax[f]*10+(fmt[0]-'0');
792 ++fmt;
794 if (neg) minmax[f] = -minmax[f];
795 //fprintf(stderr, "got: %d\n", minmax[f]);
797 if (*fmt == ',') {
798 if (f == 1) { va_end(ap); return "invalid integer bounds: extra comma"; }
799 // do nothing, we are happy
800 ++fmt;
801 } else if (*fmt == '}') {
802 // ok, done
803 break;
804 } else { va_end(ap); return "invalid integer bounds"; }
806 if (*fmt != '}') { va_end(ap); return "invalid integer bounds"; }
807 ++fmt;
809 //fprintf(stderr, "b: (%d,%d) (%d,%d)\n", haveminmax[0], minmax[0], haveminmax[1], minmax[1]);
810 if ((haveminmax[0] && n < minmax[0]) || (haveminmax[1] && n > minmax[1])) { va_end(ap); return "integer out of bounds"; }
813 if (p) *p = n;
815 break;
816 case 'b': { /* bool */
817 int *p = va_arg(ap, int *);
819 trimstr(args);
820 if (!args[0]) { va_end(ap); return "invalid boolean"; }
821 if (strcasecmp(args, "true") == 0 || strcasecmp(args, "on") == 0 ||
822 strcasecmp(args, "tan") == 0 || strcasecmp(args, "1") == 0) {
823 if (p) *p = 1;
824 } else if (strcasecmp(args, "false") == 0 || strcasecmp(args, "off") == 0 ||
825 strcasecmp(args, "ona") == 0 || strcasecmp(args, "0") == 0) {
827 if (p) *p = 0;
828 } else {
829 va_end(ap);
830 return "invalid boolean";
832 } break;
833 default:
834 va_end(ap);
835 return "invalid format specifier";
838 va_end(ap);
839 while (*line && isspace(*line)) ++line;
840 if (!line[0] || line[0] == '#') return NULL;
841 return "extra args";
845 ////////////////////////////////////////////////////////////////////////////////
846 // UTF-8
847 static int utf8decode (const char *s, ulong *u) {
848 uchar c;
849 int n, rtn;
851 rtn = 1;
852 c = *s;
853 if (~c & B7) { /* 0xxxxxxx */
854 *u = c;
855 return rtn;
856 } else if ((c & (B7|B6|B5)) == (B7|B6)) { /* 110xxxxx */
857 *u = c&(B4|B3|B2|B1|B0);
858 n = 1;
859 } else if ((c & (B7|B6|B5|B4)) == (B7|B6|B5)) { /* 1110xxxx */
860 *u = c&(B3|B2|B1|B0);
861 n = 2;
862 } else if ((c & (B7|B6|B5|B4|B3)) == (B7|B6|B5|B4)) { /* 11110xxx */
863 *u = c & (B2|B1|B0);
864 n = 3;
865 } else {
866 goto invalid;
868 ++s;
869 for (int f = n; f > 0; --f, ++rtn, ++s) {
870 c = *s;
871 if ((c & (B7|B6)) != B7) goto invalid; /* 10xxxxxx */
872 *u <<= 6;
873 *u |= c & (B5|B4|B3|B2|B1|B0);
875 if ((n == 1 && *u < 0x80) ||
876 (n == 2 && *u < 0x800) ||
877 (n == 3 && *u < 0x10000) ||
878 (*u >= 0xD800 && *u <= 0xDFFF)) {
879 goto invalid;
881 return rtn;
882 invalid:
883 *u = 0xFFFD;
884 return rtn;
888 static int utf8encode (ulong u, char *s) {
889 uchar *sp;
890 ulong uc;
891 int n;
893 sp = (uchar *)s;
894 uc = u&0x1fffff;
895 if (uc < 0x80) {
896 *sp = uc; /* 0xxxxxxx */
897 return 1;
898 } else if (uc < 0x800) {
899 *sp = (uc >> 6) | (B7|B6); /* 110xxxxx */
900 n = 1;
901 } else if (uc < 0x10000) {
902 *sp = (uc >> 12) | (B7|B6|B5); /* 1110xxxx */
903 n = 2;
904 } else if (uc <= 0x10FFFF) {
905 *sp = (uc >> 18) | (B7|B6|B5|B4); /* 11110xxx */
906 n = 3;
907 } else {
908 goto invalid;
910 ++sp;
911 for (int f = n; f > 0; --f, ++sp) *sp = ((uc >> 6*(f-1)) & (B5|B4|B3|B2|B1|B0)) | B7; /* 10xxxxxx */
912 return n+1;
913 invalid:
914 /* U+FFFD */
915 *s++ = '\xEF';
916 *s++ = '\xBF';
917 *s = '\xBD';
918 return 3;
922 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
923 UTF-8 otherwise return 0 */
924 static int isfullutf8 (const char *s, int b) {
925 uchar *c1, *c2, *c3;
927 c1 = (uchar *) s;
928 c2 = (uchar *) ++s;
929 c3 = (uchar *) ++s;
930 if (b < 1) return 0;
931 if ((*c1&(B7|B6|B5)) == (B7|B6) && b == 1) return 0;
932 if ((*c1&(B7|B6|B5|B4)) == (B7|B6|B5) && (b == 1 || (b == 2 && (*c2&(B7|B6)) == B7))) return 0;
933 if ((*c1&(B7|B6|B5|B4|B3)) == (B7|B6|B5|B4) && (b == 1 || (b == 2 && (*c2&(B7|B6)) == B7) || (b == 3 && (*c2&(B7|B6)) == B7 && (*c3&(B7|B6)) == B7))) return 0;
934 return 1;
938 static int utf8size (const char *s) {
939 uchar c = *s;
941 if (~c&B7) return 1;
942 if ((c&(B7|B6|B5)) == (B7|B6)) return 2;
943 if ((c&(B7|B6|B5|B4)) == (B7|B6|B5)) return 3;
944 return 4;
948 static int utf8strlen (const char *s) {
949 int len = 0;
951 while (*s) {
952 if (((unsigned char)(s[0])&0xc0) == 0xc0 || ((unsigned char)(s[0])&0x80) == 0) ++len;
953 ++s;
955 return len;
959 static void utf8choplast (char *s) {
960 int lastpos = 0;
962 for (char *t = s; *t; ++t) {
963 if (((unsigned char)(t[0])&0xc0) == 0xc0 || ((unsigned char)(t[0])&0x80) == 0) lastpos = (int)(t-s);
965 s[lastpos] = 0;
969 ////////////////////////////////////////////////////////////////////////////////
970 // utilities
971 static char *SPrintfVA (const char *fmt, va_list vaorig) {
972 char *buf = NULL;
973 int olen, len = 128;
975 buf = malloc(len);
976 if (buf == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
977 for (;;) {
978 char *nb;
979 va_list va;
981 va_copy(va, vaorig);
982 olen = vsnprintf(buf, len, fmt, va);
983 va_end(va);
984 if (olen >= 0 && olen < len) return buf;
985 if (olen < 0) olen = len*2-1;
986 nb = realloc(buf, olen+1);
987 if (nb == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
988 buf = nb;
989 len = olen+1;
994 static __attribute__((format(printf,1,2))) char *SPrintf (const char *fmt, ...) {
995 char *buf = NULL;
996 va_list va;
998 va_start(va, fmt);
999 buf = SPrintfVA(fmt, va);
1000 va_end(va);
1001 return buf;
1005 static __attribute__((noreturn)) __attribute__((format(printf,1,2))) void die (const char *errstr, ...) {
1006 va_list ap;
1008 fprintf(stderr, "FATAL: ");
1009 va_start(ap, errstr);
1010 vfprintf(stderr, errstr, ap);
1011 va_end(ap);
1012 fprintf(stderr, "\n");
1013 exit(EXIT_FAILURE);
1017 ////////////////////////////////////////////////////////////////////////////////
1018 // getticks
1019 static struct timespec mclk_sttime; // starting time of monotonic clock
1022 static void mclock_init (void) {
1023 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &mclk_sttime);
1027 static uint mclock_ticks (void) {
1028 struct timespec tp;
1030 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &tp);
1031 tp.tv_sec -= mclk_sttime.tv_sec;
1032 return tp.tv_sec*1000+(tp.tv_nsec/1000000);
1036 ////////////////////////////////////////////////////////////////////////////////
1037 // locale conversions
1038 static iconv_t icFromLoc;
1039 static iconv_t icToLoc;
1040 static int needConversion = 0;
1041 static const char *cliLocale = NULL;
1044 static void initLCConversion (void) {
1045 const char *lct = setlocale(LC_CTYPE, NULL);
1046 char *cstr;
1048 needConversion = 0;
1049 if (cliLocale == NULL) {
1050 if (strrchr(lct, '.') != NULL) lct = strrchr(lct, '.')+1;
1051 } else {
1052 lct = cliLocale;
1054 if (strcasecmp(lct, "utf8") == 0 || strcasecmp(lct, "utf-8") == 0) return;
1055 //fprintf(stderr, "locale: [%s]\n", lct);
1056 icFromLoc = iconv_open("UTF-8", lct);
1057 if (icFromLoc == (iconv_t)-1) die("can't initialize locale conversion");
1058 cstr = SPrintf("%s//TRANSLIT", lct);
1059 icToLoc = iconv_open(cstr, "UTF-8");
1060 free(cstr);
1061 if (icToLoc == (iconv_t)-1) die("can't initialize locale conversion");
1062 needConversion = 1;
1066 static int loc2utf (char *dest, const char *src, int len) {
1067 if (needConversion) {
1068 char *ibuf, *obuf;
1069 size_t il, ol, ool;
1071 ibuf = (char *)src;
1072 obuf = dest;
1073 il = len;
1074 ool = ol = il*4;
1075 il = iconv(icFromLoc, &ibuf, &il, &obuf, &ol);
1076 if (il == (size_t)-1) return 0;
1077 return ool-ol;
1078 } else {
1079 if (len > 0) memmove(dest, src, len);
1080 return len;
1085 static int utf2loc (char *dest, const char *src, int len) {
1086 if (needConversion) {
1087 char *ibuf, *obuf;
1088 size_t il, ol, ool;
1090 ibuf = (char *)src;
1091 obuf = dest;
1092 il = len;
1093 ool = ol = il*4;
1094 il = iconv(icToLoc, &ibuf, &il, &obuf, &ol);
1095 if (il == (size_t)-1) return 0;
1096 return ool-ol;
1097 } else {
1098 if (len > 0) memmove(dest, src, len);
1099 return len;
1104 ////////////////////////////////////////////////////////////////////////////////
1105 static void fixWindowTitle (const Term *t) {
1106 const char *title = (t != NULL) ? t->title : NULL;
1108 if (title == NULL || !title[0]) {
1109 title = opt_title;
1110 if (title == NULL) title = "";
1112 XStoreName(xw.dpy, xw.win, title);
1113 XChangeProperty(xw.dpy, xw.win, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, (const unsigned char *)title, strlen(title));
1117 // find latest active terminal (but not current %-)
1118 static int findTermToSwitch (void) {
1119 int maxlat = -1, idx = -1;
1121 for (int f = 0; f < term_count; ++f) {
1122 if (term != term_array[f] && term_array[f]->lastActiveTime > maxlat) {
1123 maxlat = term_array[f]->lastActiveTime;
1124 idx = f;
1127 if (idx < 0) {
1128 if (termidx == 0) idx = 0; else idx = termidx+1;
1129 if (idx > term_count) idx = term_count-1;
1131 return idx;
1135 static void switchToTerm (int idx, int redraw) {
1136 if (idx >= 0 && idx < term_count && term_array[idx] != NULL && term_array[idx] != term) {
1137 int tt = mclock_ticks();;
1139 if (term != NULL) term->lastActiveTime = tt;
1140 termidx = idx;
1141 term = term_array[termidx];
1142 term->curbhidden = 0;
1143 term->lastBlinkTime = tt;
1144 xseturgency(0);
1145 tfulldirt();
1146 fixWindowTitle(term);
1147 updateTabBar = 1;
1148 if (redraw) draw(1);
1149 //FIXME: optimize memory allocations
1150 if (term->sel.clip != NULL && term->sel.bx >= 0) {
1151 if (lastSelStr != NULL) free(lastSelStr);
1152 lastSelStr = strdup(term->sel.clip);
1153 //fprintf(stderr, "newsel: [%s]\n", lastSelStr);
1155 xfixsel();
1156 //fprintf(stderr, "term #%d\n", termidx);
1157 //fprintf(stderr, "needConv: %d\n", term->needConv);
1162 static Term *termalloc (void) {
1163 Term *t;
1165 if (term_count >= term_array_size) {
1166 int newsz = (term_count==0) ? 1 : term_array_size+64;
1167 Term **n = realloc(term_array, sizeof(Term *)*newsz);
1169 if (n == NULL && term_count == 0) die("out of memory!");
1170 term_array = n;
1171 term_array_size = newsz;
1173 if ((t = calloc(1, sizeof(Term))) == NULL) return NULL;
1174 t->wrbufsize = WBUFSIZ;
1175 t->deffg = defaultFG;
1176 t->defbg = defaultBG;
1177 t->dead = 1;
1178 t->needConv = (needConversion ? 1 : 0);
1179 t->belltype = (opt_audiblebell?BELL_AUDIO:0)|(opt_urgentbell?BELL_URGENT:0);
1180 t->curblink = opt_cursorBlink;
1181 t->curblinkinactive = opt_cursorBlinkInactive;
1182 term_array[term_count++] = t;
1183 return t;
1187 // newer delete last terminal!
1188 static void termfree (int idx) {
1189 if (idx >= 0 && idx < term_count && term_array[idx] != NULL) {
1190 Term *t = term_array[idx];
1192 if (t->pid != 0) {
1193 kill(t->pid, SIGKILL);
1194 return;
1196 if (t->cmdfd >= 0) {
1197 close(t->cmdfd);
1198 t->cmdfd = -1;
1200 exitcode = t->exitcode;
1201 if (idx == termidx) {
1202 if (term_count > 1) {
1203 t->dead = 1;
1204 //switchToTerm((idx > 0) ? idx-1 : 1, 0);
1205 switchToTerm(findTermToSwitch(), 0);
1206 return;
1208 term = NULL;
1210 for (int y = 0; y < t->row; ++y) free(t->alt[y]);
1211 for (int y = 0; y < t->linecount; ++y) {
1212 //fprintf(stderr, "y=%d\n", y);
1213 free(t->line[y]);
1215 free(t->dirty);
1216 free(t->alt);
1217 free(t->line);
1218 if (t->execcmd != NULL) free(t->execcmd);
1219 // condense array
1220 if (termidx > idx) {
1221 // not current, and current at the right
1222 --termidx;
1224 for (int f = idx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
1225 --term_count;
1226 XFreePixmap(xw.dpy, t->picbuf);
1227 free(t);
1232 static void termcleanup (void) {
1233 int f = 0, needredraw = 0;
1235 while (f < term_count) {
1236 if (term_array[f]->dead) {
1237 termfree(f);
1238 needredraw = 1;
1239 } else {
1240 ++f;
1243 if (needredraw) {
1244 updateTabBar = 1;
1245 draw(1);
1250 //FIXME: is it safe to assume that signal interrupted main program?
1251 static void sigchld (int a) {
1252 //if (waitpid(term->pid, &stat, 0) < 0) die("waiting for pid %hd failed: %s", term->pid, SERRNO);
1253 for (;;) {
1254 int stat = 0;
1255 pid_t res = waitpid(-1, &stat, WNOHANG);
1257 if (res == (pid_t)-1 || res == 0) break;
1259 for (int f = 0; f < term_count; ++f) {
1260 if (term_array[f]->pid == res) {
1261 // this terminal should die
1262 //if (term_count == 1) exit(0);
1263 //close(term_array[f]->cmdfd);
1264 //term_array[f]->cmdfd = -1;
1265 term_array[f]->dead = 1;
1266 term_array[f]->pid = 0;
1267 term_array[f]->exitcode = (WIFEXITED(stat)) ? WEXITSTATUS(stat) : 127;
1268 //fprintf(stderr, "exitcode=%d\n", term_array[f]->exitcode);
1269 updateTabBar = 1;
1270 break;
1274 signal(SIGCHLD, sigchld);
1278 ////////////////////////////////////////////////////////////////////////////////
1279 static void keytrans_reset (void) {
1280 if (keytrans) free(keytrans);
1281 keytrans = NULL;
1282 keytrans_size = 0;
1283 keytrans_used = 0;
1287 static void keytrans_add (const char *src, const char *dst) {
1288 KeySym kssrc = XStringToKeysym(src);
1289 KeySym ksdst = XStringToKeysym(dst);
1291 if (kssrc == NoSymbol) die("invalid keysym: '%s'", src);
1292 if (ksdst == NoSymbol) die("invalid keysym: '%s'", dst);
1293 if (kssrc == ksdst) return; // idiot
1295 for (int f = 0; f < keytrans_used; ++f) {
1296 if (keytrans[f].src == kssrc) {
1297 // replace
1298 keytrans[f].dst = ksdst;
1299 return;
1303 if (keytrans_used >= keytrans_size) {
1304 int newsize = keytrans_size+64;
1305 KeyTransDef *n = realloc(keytrans, sizeof(KeyTransDef)*newsize);
1307 if (n == NULL) die("out of memory");
1308 keytrans_size = newsize;
1309 keytrans = n;
1311 keytrans[keytrans_used].src = kssrc;
1312 keytrans[keytrans_used].dst = ksdst;
1313 ++keytrans_used;
1317 ////////////////////////////////////////////////////////////////////////////////
1318 static void parsekeyname (const char *str, KeySym *ks, uint *mask, int *kp) {
1319 char *s = alloca(strlen(str)+1);
1321 if (s == NULL) die("out of memory");
1322 strcpy(s, str);
1323 *kp = 0;
1324 *ks = NoSymbol;
1325 *mask = XK_NO_MOD;
1327 while (*s) {
1328 char *e, oc;
1329 uint mm = 0;
1330 int mod = 1;
1332 while (*s && isspace(*s)) ++s;
1333 for (e = s; *e && !isspace(*e) && *e != '+'; ++e) ;
1334 oc = *e; *e = 0;
1336 if (strcasecmp(s, "alt") == 0) mm = Mod1Mask;
1337 else if (strcasecmp(s, "win") == 0) mm = Mod4Mask;
1338 else if (strcasecmp(s, "ctrl") == 0) mm = ControlMask;
1339 else if (strcasecmp(s, "shift") == 0) mm = ShiftMask;
1340 else if (strcasecmp(s, "any") == 0) mm = XK_NO_MOD; //!
1341 else if (strcasecmp(s, "kpad") == 0) *kp = 1;
1342 else {
1343 mod = 0;
1344 if ((*ks = XStringToKeysym(s)) == NoSymbol) break;
1345 //fprintf(stderr, "[%s]\n", s);
1348 *e = oc;
1349 s = e;
1350 while (*s && isspace(*s)) ++s;
1351 if (mod) {
1352 if (*s != '+') { *ks = NoSymbol; break; }
1353 ++s;
1354 if (mm != 0) {
1355 if (mm == XK_NO_MOD) *mask = XK_ANY_MOD;
1356 else if (*mask == XK_NO_MOD) *mask = mm;
1357 else if (*mask != XK_ANY_MOD) *mask |= mm;
1359 } else {
1360 if (*s) { *ks = NoSymbol; break; }
1363 if (*ks == NoSymbol) die("invalid key name: '%s'", str);
1364 //fprintf(stderr, "mask=0x%08x, kp=%d\n", *mask, *kp);
1368 ////////////////////////////////////////////////////////////////////////////////
1369 static void keybinds_reset (void) {
1370 if (keybinds) free(keybinds);
1371 keybinds = NULL;
1372 keybinds_size = 0;
1373 keybinds_used = 0;
1377 static void keybind_add (const char *key, const char *act) {
1378 KeySym ks;
1379 uint mask;
1380 int kp;
1382 parsekeyname(key, &ks, &mask, &kp);
1384 for (int f = 0; f < keybinds_used; ++f) {
1385 if (keybinds[f].key == ks && keybinds[f].mask == mask) {
1386 // replace or remove
1387 free(keybinds[f].str);
1388 if (act == NULL || !act[0]) {
1389 // remove
1390 for (int c = f+1; c < keybinds_used; ++c) keybinds[c-1] = keybinds[c];
1391 } else {
1392 // replace
1393 if ((keybinds[f].str = strdup(act)) == NULL) die("out of memory");
1395 return;
1399 if (keybinds_used >= keybinds_size) {
1400 int newsize = keybinds_size+64;
1401 KeyInfoDef *n = realloc(keybinds, sizeof(KeyInfoDef)*newsize);
1403 if (n == NULL) die("out of memory");
1404 keybinds_size = newsize;
1405 keybinds = n;
1407 keybinds[keybinds_used].key = ks;
1408 keybinds[keybinds_used].mask = mask;
1409 keybinds[keybinds_used].kp = 0;
1410 if ((keybinds[keybinds_used].str = strdup(act)) == NULL) die("out of memory");
1411 ++keybinds_used;
1415 ////////////////////////////////////////////////////////////////////////////////
1416 static void keymap_reset (void) {
1417 if (keymap) free(keymap);
1418 keymap = NULL;
1419 keymap_size = 0;
1420 keymap_used = 0;
1424 static void keymap_add (const char *key, const char *act) {
1425 KeySym ks;
1426 uint mask;
1427 int kp;
1429 parsekeyname(key, &ks, &mask, &kp);
1431 for (int f = 0; f < keymap_used; ++f) {
1432 if (keymap[f].key == ks && keymap[f].mask == mask && keymap[f].kp == kp) {
1433 // replace or remove
1434 free(keymap[f].str);
1435 if (act == NULL) {
1436 // remove
1437 for (int c = f+1; c < keymap_used; ++c) keymap[c-1] = keymap[c];
1438 } else {
1439 // replace
1440 if ((keymap[f].str = strdup(act)) == NULL) die("out of memory");
1442 return;
1446 if (keymap_used >= keymap_size) {
1447 int newsize = keymap_size+128;
1448 KeyInfoDef *n = realloc(keymap, sizeof(KeyInfoDef)*newsize);
1450 if (n == NULL) die("out of memory");
1451 keymap_size = newsize;
1452 keymap = n;
1454 keymap[keymap_used].key = ks;
1455 keymap[keymap_used].mask = mask;
1456 keymap[keymap_used].kp = kp;
1457 if ((keymap[keymap_used].str = strdup(act)) == NULL) die("out of memory");
1458 ++keymap_used;
1462 ////////////////////////////////////////////////////////////////////////////////
1463 // selection
1464 static void inline markDirty (int lineno, int flag) {
1465 if (term != NULL && lineno >= 0 && lineno < term->row) {
1466 term->dirty[lineno] |= flag;
1467 term->wantRedraw = 1;
1468 term->lastDrawTime = mclock_ticks(); //FIXME: avoid excess redraw?
1473 static void selinit (void) {
1474 term->sel.tclick1 = term->sel.tclick2 = mclock_ticks();
1475 term->sel.mode = 0;
1476 term->sel.bx = -1;
1477 term->sel.clip = NULL;
1478 term->sel.xtarget = XA_UTF8;
1479 //if (term->sel.xtarget == None) term->sel.xtarget = XA_STRING;
1483 static void selhide (void) {
1484 if (term->sel.bx != -1) {
1485 term->sel.mode = 0;
1486 term->sel.bx = -1;
1487 tfulldirt();
1492 static inline int selected (int x, int y) {
1493 if (term->sel.bx == -1) return 0;
1495 if (term->sel.ey == y && term->sel.by == y) {
1496 int bx = MIN(term->sel.bx, term->sel.ex);
1497 int ex = MAX(term->sel.bx, term->sel.ex);
1499 return BETWEEN(x, bx, ex);
1502 return
1503 ((term->sel.b.y < y && y < term->sel.e.y) || (y == term->sel.e.y && x <= term->sel.e.x)) ||
1504 (y == term->sel.b.y && x >= term->sel.b.x && (x <= term->sel.e.x || term->sel.b.y != term->sel.e.y));
1508 static void getbuttoninfo (XEvent *e, int *b, int *x, int *y) {
1509 if (b != NULL) *b = e->xbutton.button;
1510 if (x != NULL) *x = X2COL(e->xbutton.x);
1511 if (y != NULL) *y = Y2ROW(e->xbutton.y);
1512 term->sel.b.x = (term->sel.by < term->sel.ey ? term->sel.bx : term->sel.ex);
1513 term->sel.b.y = MIN(term->sel.by, term->sel.ey);
1514 term->sel.e.x = (term->sel.by < term->sel.ey ? term->sel.ex : term->sel.bx);
1515 term->sel.e.y = MAX(term->sel.by, term->sel.ey);
1519 static void mousereport (XEvent *e) {
1520 int x = X2COL(e->xbutton.x);
1521 int y = Y2ROW(e->xbutton.y);
1522 int button = e->xbutton.button;
1523 int state = e->xbutton.state;
1524 //char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
1525 char buf[32], *p;
1526 char lastCh = 'M';
1527 int ss;
1529 if (term == NULL) return;
1530 if (x < 0 || x >= term->col || y < 0 || y >= term->row) return;
1532 #if 0
1533 case 1000: /* X11 xterm mouse reporting */
1534 case 1005: /* utf-8 mouse encoding */
1535 case 1006: /* sgr mouse encoding */
1536 case 1015: /* urxvt mouse encoding */
1537 #endif
1538 //sprintf(buf, "\x1b[M%c%c%c", 0, 32+x+1, 32+y+1);
1539 p = buf+sprintf(buf, "\x1b[M");
1540 /* from urxvt */
1541 if (e->xbutton.type == MotionNotify) {
1542 if (!IS_SET(MODE_MOUSEMOTION) || (x == term->mouseox && y == term->mouseoy)) return;
1543 button = term->mouseob+32;
1544 term->mouseox = x;
1545 term->mouseoy = y;
1546 } else if (e->xbutton.type == ButtonRelease || button == AnyButton) {
1547 if (term->mousemode != 1006) {
1548 button = 3; // 'release' flag
1549 } else {
1550 lastCh = 'm';
1551 button -= Button1;
1552 if (button >= 3) button += 64-3;
1554 } else {
1555 button -= Button1;
1556 if (button >= 3) button += 64-3;
1557 if (e->xbutton.type == ButtonPress) {
1558 term->mouseob = button;
1559 term->mouseox = x;
1560 term->mouseoy = y;
1563 ss = (state & ShiftMask ? 4 : 0)+(state & Mod4Mask ? 8 : 0)+(state & ControlMask ? 16 : 0);
1564 switch (term->mousemode) {
1565 case 1006: /* sgr */
1566 buf[2] = '<';
1567 p += sprintf(p, "%d;", button+ss);
1568 break;
1569 case 1015: /* urxvt */
1570 --p; // remove 'M'
1571 p += sprintf(p, "%d;", 32+button+ss);
1572 break;
1573 default:
1574 *p++ = 32+button+ss;
1575 break;
1577 // coords
1578 switch (term->mousemode) {
1579 case 1005: /* utf-8 */
1580 p += utf8encode(x+1, p);
1581 p += utf8encode(y+1, p);
1582 break;
1583 case 1006: /* sgr */
1584 p += sprintf(p, "%d;%d%c", x+1, y+1, lastCh);
1585 break;
1586 case 1015: /* urxvt */
1587 p += sprintf(p, "%d;%dM", x+1, y+1);
1588 break;
1589 default:
1590 p += sprintf(p, "%c%c", 32+x+1, 32+y+1);
1591 break;
1593 *p = 0;
1596 fprintf(stderr, "(%d)<", term->mousemode);
1597 for (const unsigned char *s = (const unsigned char *)buf; *s; ++s) {
1598 if (s[0] < 32) fprintf(stderr, "{%d}", s[0]); else fputc(s[0], stderr);
1600 fputs(">\n", stderr);
1603 ttywritestr(buf);
1607 static void xfixsel (void) {
1608 if (lastSelStr != NULL) {
1609 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
1610 XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, xw.win, CurrentTime);
1611 } else {
1612 if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) == xw.win) XSetSelectionOwner(xw.dpy, XA_PRIMARY, None, CurrentTime);
1613 if (XGetSelectionOwner(xw.dpy, XA_CLIPBOARD) == xw.win) XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, None, CurrentTime);
1615 XFlush(xw.dpy);
1619 static void xsetsel (char *str) {
1620 /* register the selection for both the clipboard and the primary */
1621 if (term == NULL) return;
1622 if (term->sel.clip != NULL) free(term->sel.clip);
1623 term->sel.clip = str;
1624 if (lastSelStr != NULL) free(lastSelStr);
1625 lastSelStr = (str != NULL ? strdup(str) : NULL);
1626 xfixsel();
1627 //fprintf(stderr, "[%s]\n", str);
1631 static void selclear (XEvent *e) {
1632 if (lastSelStr != NULL) free(lastSelStr);
1633 lastSelStr = NULL;
1634 if (term != NULL) {
1635 if (term->sel.clip != NULL) free(term->sel.clip);
1636 term->sel.clip = NULL;
1637 term->sel.mode = 0;
1638 if (term->sel.bx != 0) {
1639 term->sel.bx = -1;
1640 tfulldirt();
1641 draw(1);
1647 static Line selgetlinebyy (int y) {
1648 Line l;
1650 if (y >= term->row) return NULL;
1651 if (y < 0) {
1652 if (y < -(term->maxhistory)) return NULL;
1653 l = term->line[term->row+(-y)-1];
1654 } else {
1655 l = term->line[y];
1657 return l;
1661 static void selcopy (void) {
1662 char *str, *ptr;
1663 int x, y, bufsize, is_selected = 0;
1665 if (term == NULL || term->sel.bx == -1) {
1666 str = NULL;
1667 } else {
1668 int sy = MIN(term->sel.e.y, term->sel.b.y);
1669 int ey = MAX(term->sel.e.y, term->sel.b.y);
1672 fprintf(stderr, "bx=%d; ex=%d; by=%d; ey=%d\n", term->sel.bx, term->sel.by, term->sel.ex, term->sel.ey);
1673 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);
1674 fprintf(stderr, " sy=%d; ey=%d\n", sy, ey);
1676 if (ey >= term->row) { selclear(NULL); return; }
1677 bufsize = (term->col+1)*(ey-sy+1)*UTF_SIZ;
1678 ptr = str = malloc(bufsize);
1679 /* append every set & selected glyph to the selection */
1680 for (y = sy; y <= ey; ++y) {
1681 Line l = selgetlinebyy(y);
1682 char *pstart = ptr;
1684 if (l == NULL) continue;
1685 for (x = 0; x < term->col; ++x) {
1686 if ((is_selected = selected(x, y)) != 0) {
1687 int size = utf8size(l[x].c);
1689 //if (size == 1) fprintf(stderr, "x=%d; y=%d; size=%d; c=%d\n", x, y, size, l[x].c[0]);
1690 if (size == 1) {
1691 unsigned char c = (unsigned char)l[x].c[0];
1693 *ptr = (c < 32 || c >= 127) ? ' ' : c;
1694 } else {
1695 memcpy(ptr, l[x].c, size);
1697 ptr += size;
1700 //fprintf(stderr, "y=%d; linebytes=%d\n", y, (int)(ptr-pstart));
1701 // trim trailing spaces
1702 while (ptr > pstart && ptr[-1] == ' ') --ptr;
1703 // \n at the end of every unwrapped selected line except for the last one
1704 if (is_selected && y < ey && selected(term->col-1, y) && !(l[term->col-1].state&GLYPH_WRAP)) *ptr++ = '\n';
1706 *ptr = 0;
1708 xsetsel(str);
1709 if (!str || !str[0]) selclear(NULL);
1713 static void selnotify (XEvent *e) {
1714 ulong nitems, ofs, rem;
1715 int format;
1716 uchar *data;
1717 Atom type;
1718 XSelectionEvent *se = (XSelectionEvent *)e;
1719 int isutf8;
1720 int wasbrk = 0;
1721 char *ucbuf = NULL;
1722 int ucbufsize = 0;
1724 if (term == NULL || term->cmdMode == CMDMODE_MESSAGE) return;
1725 #ifdef PASTE_SELECTION_DEBUG
1727 char *name;
1729 fprintf(stderr, "selnotify!\n");
1731 name = se->selection != None ? XGetAtomName(se->display, se->selection) : NULL;
1732 fprintf(stderr, " selection: [%s]\n", name);
1733 if (name != NULL) XFree(name);
1735 name = se->target != None ? XGetAtomName(se->display, se->target) : NULL;
1736 fprintf(stderr, " target: [%s]\n", name);
1737 if (name != NULL) XFree(name);
1739 name = se->property != None ? XGetAtomName(se->display, se->property) : NULL;
1740 fprintf(stderr, " property: [%s]\n", name);
1741 if (name != NULL) XFree(name);
1743 #endif
1745 if (se->property != XA_VT_SELECTION) return;
1747 #ifdef PASTE_SELECTION_DEBUG
1749 fprintf(stderr, "selection:\n");
1750 fprintf(stderr, " primary: %d\n", se->selection == XA_PRIMARY);
1751 fprintf(stderr, " secondary: %d\n", se->selection == XA_SECONDARY);
1752 fprintf(stderr, " clipboard: %d\n", se->selection == XA_CLIPBOARD);
1753 fprintf(stderr, " vtsel: %d\n", se->selection == XA_VT_SELECTION);
1754 fprintf(stderr, "target:\n");
1755 fprintf(stderr, " primary: %d\n", se->target == XA_PRIMARY);
1756 fprintf(stderr, " secondary: %d\n", se->target == XA_SECONDARY);
1757 fprintf(stderr, " clipboard: %d\n", se->target == XA_CLIPBOARD);
1758 fprintf(stderr, " vtsel: %d\n", se->target == XA_VT_SELECTION);
1760 #endif
1761 if (se->target == XA_UTF8) {
1762 isutf8 = 1;
1763 } else if (se->target == XA_STRING) {
1764 isutf8 = 0;
1765 } else if (se->target == XA_TARGETS) {
1766 Atom rqtype = None, *targ;
1768 if (XGetWindowProperty(xw.dpy, xw.win, se->property, 0, 65536, False, XA_ATOM, &type, &format, &nitems, &rem, &data)) {
1769 //fprintf(stderr, "no targets\n");
1770 rqtype = XA_STRING;
1771 } else {
1772 for (targ = (Atom *)data; nitems > 0; --nitems, ++targ) {
1773 #ifdef PASTE_SELECTION_DEBUG
1774 fprintf(stderr, " TGT: [%s]\n", XGetAtomName(se->display, *targ));
1775 #endif
1776 if (*targ == XA_UTF8) rqtype = XA_UTF8;
1777 else if (*targ == XA_STRING && rqtype == None) rqtype = XA_STRING;
1779 XFree(data);
1781 if (rqtype != None) XConvertSelection(xw.dpy, se->selection, rqtype, XA_VT_SELECTION, xw.win, CurrentTime);
1782 return;
1783 } else {
1784 return;
1787 ofs = 0;
1788 do {
1789 int blen;
1790 char *str;
1792 if (XGetWindowProperty(xw.dpy, xw.win, se->property, ofs, BUFSIZ/4, False, AnyPropertyType, &type, &format, &nitems, &rem, &data)) {
1793 fprintf(stderr, "Clipboard allocation failed\n");
1794 break;
1796 //fprintf(stderr, "nitems=%d; format=%d; rem=%d\n", (int)nitems, format, (int)rem);
1797 blen = nitems*format/8;
1799 if (!isutf8) {
1800 int newsz = blen*4+64;
1802 if (ucbufsize < newsz) {
1803 char *n = realloc(ucbuf, newsz);
1805 if (n == NULL) { XFree(data); break; }
1806 ucbuf = n;
1807 ucbufsize = newsz;
1810 blen = loc2utf(ucbuf, (const char *)data, blen);
1811 str = ucbuf;
1812 } else {
1813 str = (char *)data;
1816 if (term->cmdMode != CMDMODE_NONE) {
1817 tcmdput(str, blen);
1818 } else {
1819 if (nitems*format/8 > 0 && !wasbrk && IS_SET(MODE_BRACPASTE)) {
1820 wasbrk = 1;
1821 ttywritestr("\x1b[200~");
1823 ttywrite(str, blen);
1825 XFree(data);
1826 /* number of 32-bit chunks returned */
1827 ofs += nitems*format/32;
1828 } while (rem > 0);
1830 if (wasbrk) ttywritestr("\x1b[201~");
1831 if (ucbuf != NULL) free(ucbuf);
1835 static void selpaste (Atom which) {
1836 if (term == NULL) return;
1837 if (XGetSelectionOwner(xw.dpy, which) == None) return;
1838 //XConvertSelection(xw.dpy, which, term->sel.xtarget, XA_VT_SELECTION, xw.win, CurrentTime);
1839 XConvertSelection(xw.dpy, which, XA_TARGETS, XA_VT_SELECTION, xw.win, CurrentTime);
1841 if (which == XA_PRIMARY) selpaste(XA_SECONDARY);
1842 else if (which == XA_SECONDARY) selpaste(XA_CLIPBOARD);
1847 static void selrequest (XEvent *e) {
1848 XSelectionRequestEvent *xsre;
1849 XSelectionEvent xev;
1851 if (lastSelStr == NULL) return;
1852 xsre = (XSelectionRequestEvent *)e;
1853 xev.type = SelectionNotify;
1854 xev.requestor = xsre->requestor;
1855 xev.selection = xsre->selection;
1856 xev.target = xsre->target;
1857 xev.time = xsre->time;
1858 /* reject */
1859 xev.property = None;
1860 if (xsre->target == XA_TARGETS) {
1861 /* respond with the supported type */
1862 Atom tlist[3] = {XA_UTF8, XA_STRING, XA_TARGETS};
1864 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_ATOM, 32, PropModeReplace, (uchar *)tlist, 3);
1865 xev.property = xsre->property;
1866 } else if (xsre->target == XA_UTF8 && lastSelStr != NULL) {
1867 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_UTF8, 8, PropModeReplace, (uchar *)lastSelStr, strlen(lastSelStr));
1868 xev.property = xsre->property;
1869 } else if (xsre->target == XA_STRING && lastSelStr != NULL) {
1870 char *s = malloc(strlen(lastSelStr)*4+8);
1872 if (s != NULL) {
1873 int len = utf2loc(s, lastSelStr, strlen(lastSelStr));
1875 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_STRING, 8, PropModeReplace, (uchar *)s, len);
1876 xev.property = xsre->property;
1877 free(s);
1880 /* all done, send a notification to the listener */
1881 if (!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *)&xev)) fprintf(stderr, "Error sending SelectionNotify event\n");
1885 static void bpress (XEvent *e) {
1886 if (term == NULL) return;
1888 switch (opt_tabposition) {
1889 case 0: // bottom
1890 if (e->xbutton.y >= xw.h-xw.tabheight) return;
1891 break;
1892 case 1: // top
1893 if (e->xbutton.y < xw.tabheight) return;
1894 break;
1897 if ((e->xbutton.state&ShiftMask) != 0) {
1898 if (e->xbutton.button == Button1) {
1899 if (term->sel.bx != -1) tsetdirt(term->sel.b.y, term->sel.e.y);
1900 term->sel.mode = 1;
1901 term->sel.ex = term->sel.bx = X2COL(e->xbutton.x);
1902 term->sel.ey = term->sel.by = Y2ROW(e->xbutton.y);
1903 //fprintf(stderr, "x=%d; y=%d\n", term->sel.bx, term->sel.by);
1904 draw(1);
1905 return;
1908 if (e->xbutton.button == Button3) {
1909 term->sel.bx = -1;
1910 selcopy();
1911 draw(1);
1914 return;
1916 if (IS_SET(MODE_MOUSE)) mousereport(e);
1920 static void brelease (XEvent *e) {
1921 if (term == NULL) return;
1923 switch (opt_tabposition) {
1924 case 0: // bottom
1925 if (e->xbutton.y >= xw.h-xw.tabheight) return;
1926 break;
1927 case 1: // top
1928 if (e->xbutton.y < xw.tabheight) return;
1929 break;
1932 if ((e->xbutton.state&ShiftMask) == 0 && !term->sel.mode) {
1933 if (IS_SET(MODE_MOUSE)) mousereport(e);
1934 return;
1937 if (e->xbutton.button == Button2) {
1938 selpaste(XA_PRIMARY);
1939 } else if (e->xbutton.button == Button1) {
1940 term->sel.mode = 0;
1941 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey); // this sets sel.b and sel.e
1943 if (term->sel.bx == term->sel.ex && term->sel.by == term->sel.ey) {
1944 // single line, single char selection
1945 int now;
1947 markDirty(term->sel.ey, 2);
1948 term->sel.bx = -1;
1949 now = mclock_ticks();
1950 if (now-term->sel.tclick2 <= opt_tripleclick_timeout) {
1951 /* triple click on the line */
1952 term->sel.b.x = term->sel.bx = 0;
1953 term->sel.e.x = term->sel.ex = term->col;
1954 term->sel.b.y = term->sel.e.y = term->sel.ey;
1955 } else if (now-term->sel.tclick1 <= opt_doubleclick_timeout) {
1956 /* double click to select word */
1957 Line l = selgetlinebyy(term->sel.ey);
1959 if (l != NULL) {
1960 //FIXME: write better word selection code
1961 term->sel.bx = term->sel.ex;
1962 if (IS_GFX(l[term->sel.bx].attr)) {
1963 while (term->sel.bx > 0 && IS_GFX(l[term->sel.bx-1].attr)) --term->sel.bx;
1964 term->sel.b.x = term->sel.bx;
1965 while (term->sel.ex < term->col-1 && IS_GFX(l[term->sel.ex+1].attr)) ++term->sel.ex;
1966 } else {
1967 while (term->sel.bx > 0 && !IS_GFX(l[term->sel.bx-1].attr) && l[term->sel.bx-1].c[0] != ' ') --term->sel.bx;
1968 term->sel.b.x = term->sel.bx;
1969 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;
1971 term->sel.e.x = term->sel.ex;
1972 term->sel.b.y = term->sel.e.y = term->sel.ey;
1976 selcopy();
1977 draw(1);
1978 } else {
1979 // multiline or multichar selection
1980 selcopy();
1983 term->sel.tclick2 = term->sel.tclick1;
1984 term->sel.tclick1 = mclock_ticks();
1985 //draw(1);
1989 static void bmotion (XEvent *e) {
1990 if (term == NULL) return;
1992 switch (opt_tabposition) {
1993 case 0: // bottom
1994 if (e->xbutton.y >= xw.h-xw.tabheight) return;
1995 break;
1996 case 1: // top
1997 if (e->xbutton.y < xw.tabheight) return;
1998 break;
2001 if (term->sel.mode) {
2002 int oldey = term->sel.ey, oldex = term->sel.ex;
2004 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey); // this sets sel.b and sel.e
2005 if (oldey != term->sel.ey || oldex != term->sel.ex) {
2006 int starty = MIN(oldey, term->sel.ey);
2007 int endy = MAX(oldey, term->sel.ey);
2009 tsetdirt(starty, endy);
2010 draw(1);
2012 return;
2014 //if (IS_SET(MODE_MOUSE) && e->xbutton.button != 0) mousereport(e);
2018 ////////////////////////////////////////////////////////////////////////////////
2019 // tty init
2020 static inline void setWantRedraw (void) { if (term != NULL) term->wantRedraw = 1; }
2024 static void dump (char c) {
2025 static int col;
2027 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
2028 if (++col % 10 == 0) fprintf(stderr, "\n");
2033 static __attribute__((noreturn)) void execsh (const char *str) {
2034 char **args;
2036 if (str == NULL) {
2037 char *envshell = getenv("SHELL");
2039 DEFAULT(envshell, opt_shell);
2040 setenv("TERM", opt_term, 1);
2041 args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL};
2042 } else {
2043 int argc = 0;
2045 args = calloc(32768, sizeof(char *));
2046 if (args == NULL) exit(EXIT_FAILURE);
2047 while (*str) {
2048 const char *b;
2050 while (*str && isspace(*str)) ++str;
2051 if (!str[0]) break;
2053 b = str;
2054 while (*str && !isspace(*str)) {
2055 if (*str++ == '\\') {
2056 if (*str) ++str;
2060 args[argc] = calloc(str-b+1, 1);
2061 memcpy(args[argc], b, str-b);
2064 FILE *fo = fopen("z.log", "a");
2065 fprintf(fo, "%d: [%s]\n", argc, args[argc]);
2066 fclose(fo);
2069 ++argc;
2071 if (argc < 1) exit(EXIT_FAILURE);
2073 execvp(args[0], args);
2074 exit(EXIT_FAILURE);
2078 static int ttynew (Term *term) {
2079 int m, s;
2080 struct winsize w = {term->row, term->col, 0, 0};
2081 static int signalset = 0;
2083 if (openpty(&m, &s, NULL, NULL, &w) < 0) die("openpty failed: %s", SERRNO);
2084 term->cmdfd = m;
2085 ttyresize();
2086 term->cmdfd = -1;
2087 switch (term->pid = fork()) {
2088 case -1: /* error */
2089 fprintf(stderr, "fork failed");
2090 return -1;
2091 case 0: /* child */
2092 setsid(); /* create a new process group */
2093 dup2(s, STDIN_FILENO);
2094 dup2(s, STDOUT_FILENO);
2095 dup2(s, STDERR_FILENO);
2096 if (ioctl(s, TIOCSCTTY, NULL) < 0) die("ioctl TIOCSCTTY failed: %s", SERRNO);
2097 close(s);
2098 close(m);
2099 execsh(term->execcmd);
2100 break;
2101 default: /* master */
2102 close(s);
2103 term->cmdfd = m;
2104 term->dead = 0;
2105 ttyresize();
2106 if (!signalset) { signalset = 1; signal(SIGCHLD, sigchld); }
2107 break;
2109 return 0;
2113 ////////////////////////////////////////////////////////////////////////////////
2114 // tty r/w
2115 static int ttycanread (void) {
2116 for (;;) {
2117 fd_set rfd;
2118 struct timeval timeout = {0};
2120 if (term->dead || term->cmdfd < 0) return 0;
2121 FD_ZERO(&rfd);
2122 FD_SET(term->cmdfd, &rfd);
2123 if (select(term->cmdfd+1, &rfd, NULL, NULL, &timeout) < 0) {
2124 if (errno == EINTR) continue;
2125 die("select failed: %s", SERRNO);
2127 if (FD_ISSET(term->cmdfd, &rfd)) return 1;
2128 break;
2130 return 0;
2134 static int ttycanwrite (void) {
2135 for (;;) {
2136 fd_set wfd;
2137 struct timeval timeout = {0};
2139 if (term->dead || term->cmdfd < 0) return 0;
2140 FD_ZERO(&wfd);
2141 FD_SET(term->cmdfd, &wfd);
2142 if (select(term->cmdfd+1, NULL, &wfd, NULL, &timeout) < 0) {
2143 if (errno == EINTR) continue;
2144 die("select failed: %s", SERRNO);
2146 if (FD_ISSET(term->cmdfd, &wfd)) return 1;
2147 break;
2149 return 0;
2153 #ifdef DUMP_IO
2154 static void wrstr (const char *s, int len) {
2155 if (s == NULL) return;
2156 while (len-- > 0) {
2157 unsigned char c = (unsigned char)(*s++);
2159 if (c < 32) fprintf(stderr, "{%u}", c); else fwrite(&c, 1, 1, stderr);
2162 #endif
2165 static void ttyread (void) {
2166 char *ptr;
2167 int left;
2169 /* append read bytes to unprocessed bytes */
2170 if (term == NULL || term->dead || term->cmdfd < 0) return;
2171 #ifdef DUMP_PROG_OUTPUT
2172 term->xobuflen = term->obuflen;
2173 #endif
2174 left = OBUFSIZ-term->obuflen;
2175 //dlogf("0: ttyread before: free=%d, used=%d", left, term->obuflen);
2176 while (left > 0 && ttycanread()) {
2177 int ret;
2179 //if ((ret = read(term->cmdfd, term->obuf+term->obuflen, 1)) < 0) die("Couldn't read from shell: %s", SERRNO);
2180 if ((ret = read(term->cmdfd, term->obuf+term->obuflen, left)) < 0) {
2181 //fprintf(stderr, "Warning: couldn't read from tty #%d: %s\n", termidx, SERRNO);
2182 break;
2184 term->obuflen += ret;
2185 left -= ret;
2187 //dlogf("1: ttyread after: free=%d, used=%d", left, term->obuflen);
2188 /* process every complete utf8 char */
2189 #ifdef DUMP_PROG_OUTPUT
2191 FILE *fo = fopen("zlogo.log", "ab");
2192 if (fo) {
2193 fwrite(term->obuf+term->xobuflen, term->obuflen-term->xobuflen, 1, fo);
2194 fclose(fo);
2197 #endif
2198 ptr = term->obuf;
2199 if (term->needConv) {
2200 // need conversion from locale to utf-8
2201 //fprintf(stderr, "buf: %d bytes\n", term->obuflen);
2202 while (term->obuflen > 0) {
2203 char obuf[UTF_SIZ+1];
2204 int len;
2206 len = loc2utf(obuf, ptr, 1);
2207 #ifdef DUMP_IO_READ
2209 fprintf(stderr, "rdc: [");
2210 wrstr(ptr, 1);
2211 fprintf(stderr, "] --> [");
2212 wrstr(obuf, len);
2213 fprintf(stderr, "]\n");
2214 fflush(stderr);
2216 #endif
2217 if (len > 0) {
2218 obuf[len] = 0;
2219 tputc(obuf);
2221 ++ptr;
2222 --term->obuflen;
2224 term->obuflen = 0;
2225 } else {
2226 // don't do any conversion
2227 while (term->obuflen >= UTF_SIZ || isfullutf8(ptr, term->obuflen)) {
2228 ulong utf8c;
2229 char s[UTF_SIZ+1];
2230 int charsize = utf8decode(ptr, &utf8c); /* returns size of utf8 char in bytes */
2231 int len;
2233 len = utf8encode(utf8c, s);
2234 #ifdef DUMP_IO_READ
2236 fprintf(stderr, "rdx: [");
2237 wrstr(s, len);
2238 fprintf(stderr, "]\n");
2239 fflush(stderr);
2241 #endif
2242 if (len > 0) {
2243 s[len] = 0;
2244 tputc(s);
2246 ptr += charsize;
2247 term->obuflen -= charsize;
2249 //dlogf("2: ttyread afterproc: used=%d", term->obuflen);
2251 /* keep any uncomplete utf8 char for the next call */
2252 if (term->obuflen > 0) memmove(term->obuf, ptr, term->obuflen);
2256 static void ttyflushwrbuf (void) {
2257 if (term == NULL || term->dead || term->cmdfd < 0) return;
2258 if (term->wrbufpos >= term->wrbufused) {
2259 term->wrbufpos = term->wrbufused = 0;
2260 return;
2262 //dlogf("0: ttyflushwrbuf before: towrite=%d", term->wrbufused-term->wrbufpos);
2263 while (term->wrbufpos < term->wrbufused && ttycanwrite()) {
2264 int ret;
2266 if ((ret = write(term->cmdfd, term->wrbuf+term->wrbufpos, term->wrbufused-term->wrbufpos)) == -1) {
2267 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2269 term->wrbufpos += ret;
2271 if (term->wrbufpos > 0) {
2272 int left = term->wrbufused-term->wrbufpos;
2274 if (left < 1) {
2275 // write buffer is empty
2276 term->wrbufpos = term->wrbufused = 0;
2277 } else {
2278 memmove(term->wrbuf, term->wrbuf+term->wrbufpos, left);
2279 term->wrbufpos = 0;
2280 term->wrbufused = left;
2283 //dlogf("1: ttyflushwrbuf after: towrite=%d", term->wrbufused-term->wrbufpos);
2287 // convert char to locale and write it
2288 static void ttywriterawchar (const char *s, int len) {
2289 char loc[16];
2290 int clen;
2292 if (s == NULL || len < 1) return;
2293 if (term->needConv) {
2294 if ((clen = utf2loc(loc, s, len)) < 1) return;
2295 } else {
2296 if ((clen = utf8size(s)) < 1) return;
2297 memmove(loc, s, clen);
2299 #ifdef DUMP_IO_WRITE
2301 fprintf(stderr, "wrc: [");
2302 wrstr(s, len);
2303 fprintf(stderr, "] --> [");
2304 wrstr(loc, clen);
2305 fprintf(stderr, "]\n");
2306 fflush(stderr);
2308 #endif
2310 while (term->wrbufused+clen >= term->wrbufsize) {
2311 //FIXME: make write buffer dynamic?
2312 // force write at least one char
2313 //dlogf("ttywrite: forced write");
2314 if (write(term->cmdfd, term->wrbuf+term->wrbufpos, 1) == -1) {
2315 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2316 } else {
2317 ++term->wrbufpos;
2319 ttyflushwrbuf(); // make room for char
2321 memcpy(term->wrbuf+term->wrbufused, loc, clen);
2322 term->wrbufused += clen;
2326 static void ttywrite (const char *s, size_t n) {
2327 if (term == NULL || term->dead || term->cmdfd < 0) return;
2328 #ifdef DUMP_PROG_INPUT
2329 if (s != NULL && n > 0) {
2330 FILE *fo = fopen("zlogw.log", "ab");
2331 if (fo) {
2332 fwrite(s, n, 1, fo);
2333 fclose(fo);
2336 #endif
2337 //ttyflushwrbuf();
2338 if (s != NULL && n > 0) {
2339 while (n > 0) {
2340 unsigned char c = (unsigned char)(s[0]);
2342 if (term->ubufpos > 0 && isfullutf8(term->ubuf, term->ubufpos)) {
2343 // have complete char
2344 ttywriterawchar(term->ubuf, term->ubufpos);
2345 term->ubufpos = 0;
2346 continue;
2349 if (term->ubufpos == 0) {
2350 // new char
2351 if (c < 128) {
2352 ttywriterawchar(s, 1);
2353 } else if ((c&0xc0) == 0xc0) {
2354 // new utf-8 char
2355 term->ubuf[term->ubufpos++] = *s;
2356 } else {
2357 // ignore unsynced utf-8
2359 ++s;
2360 --n;
2361 continue;
2363 // char continues
2364 if (c < 128 || term->ubufpos >= UTF_SIZ || (c&0xc0) == 0xc0) {
2365 // discard previous utf-8, it's bad
2366 term->ubufpos = 0;
2367 continue;
2369 // collect
2370 term->ubuf[term->ubufpos++] = *s;
2371 ++s;
2372 --n;
2373 if (isfullutf8(term->ubuf, term->ubufpos)) {
2374 // have complete char
2375 ttywriterawchar(term->ubuf, term->ubufpos);
2376 term->ubufpos = 0;
2380 ttyflushwrbuf();
2384 ////////////////////////////////////////////////////////////////////////////////
2385 // tty resize ioctl
2386 static void ttyresize (void) {
2387 struct winsize w;
2389 if (term != NULL && term->cmdfd >= 0) {
2390 w.ws_row = term->row;
2391 w.ws_col = term->col;
2392 w.ws_xpixel = w.ws_ypixel = 0;
2393 if (ioctl(term->cmdfd, TIOCSWINSZ, &w) < 0) fprintf(stderr, "Warning: couldn't set window size: %s\n", SERRNO);
2394 setWantRedraw();
2399 ////////////////////////////////////////////////////////////////////////////////
2400 // tty utilities
2401 static void csidump (void) {
2402 printf("ESC");
2403 for (int f = 1; f < term->escseq.len; ++f) {
2404 uint c = (term->escseq.buf[f]&0xff);
2406 if (isprint(c)) putchar(c);
2407 else if (c == '\n') printf("(\\n)");
2408 else if (c == '\r') printf("(\\r)");
2409 else if (c == 0x1b) printf("(\\e)");
2410 else printf("(%02x)", c);
2412 putchar('\n');
2416 static void tsetdirt (int top, int bot) {
2417 LIMIT(top, 0, term->row-1);
2418 LIMIT(bot, 0, term->row-1);
2419 for (int y = top; y <= bot; ++y) markDirty(y, 2);
2423 static void tfulldirt (void) {
2424 tsetdirt(0, term->row-1);
2428 static void tmoveto (int x, int y) {
2429 LIMIT(x, 0, term->col-1);
2430 LIMIT(y, 0, term->row-1);
2431 term->c.state &= ~CURSOR_WRAPNEXT;
2432 if (term->c.x != x || term->c.y != y) {
2433 term->c.x = x;
2434 term->c.y = y;
2435 setWantRedraw();
2440 static void tclearregion (int x1, int y1, int x2, int y2) {
2441 int temp;
2443 //fprintf(stderr, "tclearregion: (%d,%d)-(%d,%d)\n", x1, y1, x2, y2);
2444 if (x1 > x2) { temp = x1; x1 = x2; x2 = temp; }
2445 if (y1 > y2) { temp = y1; y1 = y2; y2 = temp; }
2446 LIMIT(x1, 0, term->col-1);
2447 LIMIT(x2, 0, term->col-1);
2448 LIMIT(y1, 0, term->row-1);
2449 LIMIT(y2, 0, term->row-1);
2450 //fprintf(stderr, " tclearregion: (%d,%d)-(%d,%d)\n", x1, y1, x2, y2);
2451 for (int y = y1; y <= y2; ++y) {
2452 Line l = term->line[y];
2454 markDirty(y, (x1 <= 0 && x2 >= term->col-1) ? 2 : 1);
2455 for (int x = x1; x <= x2; ++x) {
2456 l[x].fg = term->c.attr.fg;
2457 l[x].bg = term->c.attr.bg;
2458 l[x].state = GLYPH_DIRTY;
2459 l[x].attr = ATTR_NULL|(term->c.attr.attr&(ATTR_DEFFG|ATTR_DEFBG));
2460 l[x].c[0] = ' ';
2461 if (term->sel.bx != -1 && selected(x, y)) selhide();
2463 l[term->col-1].state &= ~GLYPH_WRAP;
2468 static void tcursor (int mode) {
2469 if (mode == CURSOR_SAVE) {
2470 term->csaved = term->c;
2471 } else if (mode == CURSOR_LOAD) {
2472 term->c = term->csaved;
2473 tmoveto(term->c.x, term->c.y);
2474 setWantRedraw();
2479 static void treset (void) {
2480 Glyph g;
2482 term->c = (TCursor){{
2483 .attr = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG,
2484 .fg = 0,
2485 .bg = 0
2486 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
2487 term->c.attr.fg = term->deffg;
2488 term->c.attr.bg = term->defbg;
2490 g.state = GLYPH_DIRTY;
2491 g.attr = term->c.attr.attr;
2492 g.fg = term->c.attr.fg;
2493 g.bg = term->c.attr.bg;
2494 g.c[0] = ' ';
2495 g.c[1] = 0;
2497 term->top = 0;
2498 term->bot = term->row-1;
2499 term->mode = MODE_WRAP/* | MODE_MOUSEBTN*/;
2500 term->mousemode = 1000;
2501 term->charset = MODE_GFX0;
2502 //tclearregion(0, 0, term->col-1, term->row-1);
2503 for (int y = 0; y < term->row; ++y) {
2504 markDirty(y, 2);
2505 for (int x = 0; x < term->col; ++x) term->alt[y][x] = term->line[y][x] = g;
2507 for (int y = term->row; y < term->linecount; ++y) {
2508 for (int x = 0; x < term->col; ++x) term->line[y][x] = g;
2510 tcursor(CURSOR_SAVE);
2511 term->topline = 0;
2512 tfulldirt();
2516 static int tinitialize (int col, int row) {
2517 //memset(term, 0, sizeof(Term));
2518 //term->needConv = needConversion ? 1 : 0;
2519 term->wrbufsize = WBUFSIZ;
2520 term->deffg = term->deffg;
2521 term->defbg = term->defbg;
2522 term->row = row;
2523 term->col = col;
2524 term->dirty = calloc(term->row, sizeof(*term->dirty));
2525 term->maxhistory = opt_maxhistory;
2526 term->linecount = term->maxhistory+term->row;
2527 term->line = calloc(term->linecount, sizeof(Line));
2528 term->alt = calloc(term->row, sizeof(Line));
2529 for (int y = 0; y < term->linecount; ++y) term->line[y] = calloc(term->col, sizeof(Glyph));
2530 for (int y = 0; y < term->row; ++y) term->alt[y] = calloc(term->col, sizeof(Glyph));
2531 /* setup screen */
2532 treset();
2533 return 1;
2537 static void tadjustmaxhistory (int maxh) {
2538 if (term != NULL) {
2539 LIMIT(maxh, 0, 65535);
2540 if (term->maxhistory < maxh) {
2541 Line *nl;
2542 int newlc = term->linecount+(maxh-term->maxhistory);
2544 // add history lines
2545 if ((nl = realloc(term->line, sizeof(Line)*newlc)) != NULL) {
2546 Glyph g;
2548 term->topline = 0;
2549 term->line = nl;
2550 g.state = GLYPH_DIRTY;
2551 g.attr = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG;
2552 g.fg = term->deffg;
2553 g.bg = term->defbg;
2554 g.c[0] = ' ';
2555 g.c[1] = 0;
2556 for (int y = term->linecount; y < newlc; ++y) {
2557 term->line[y] = calloc(term->col, sizeof(Glyph));
2558 for (int x = 0; x < term->col; ++x) term->line[y][x] = g;
2560 term->maxhistory = maxh;
2561 term->linecount = newlc;
2563 } else if (term->maxhistory > maxh) {
2564 Line *nl;
2566 term->topline = 0;
2567 // remove history lines
2568 while (term->linecount > term->row+maxh) free(term->line[--term->linecount]);
2569 if ((nl = realloc(term->line, sizeof(Line)*term->linecount)) != NULL) term->line = nl;
2570 term->maxhistory = maxh;
2576 static void tswapscreen (void) {
2577 selhide();
2578 for (int f = 0; f < term->row; ++f) {
2579 Line t = term->line[f];
2581 term->line[f] = term->alt[f];
2582 term->alt[f] = t;
2584 term->mode ^= MODE_ALTSCREEN;
2585 tfulldirt();
2589 //FIXME: works bad with history
2590 //FIXME: ugly code
2591 static void selscroll (int orig, int n, int tohistory) {
2592 int docopy = 0;
2594 if (term->sel.bx == -1) return;
2596 tfulldirt(); // just in case
2597 if (!tohistory) {
2598 if (BETWEEN(term->sel.by, orig, term->bot) || BETWEEN(term->sel.ey, orig, term->bot)) {
2599 if ((term->sel.by += n) > term->bot || (term->sel.ey += n) < term->top) {
2600 selclear(NULL);
2601 return;
2603 if (term->sel.by < term->top) {
2604 term->sel.by = term->top;
2605 term->sel.bx = 0;
2606 docopy = 1;
2608 if (term->sel.ey > term->bot) {
2609 term->sel.ey = term->bot;
2610 term->sel.ex = term->col;
2611 docopy = 1;
2613 term->sel.b.x = term->sel.bx;
2614 term->sel.b.y = term->sel.by;
2615 term->sel.e.x = term->sel.ex;
2616 term->sel.e.y = term->sel.ey;
2618 } else {
2619 // tohistory!=0; always scrolls full screen up (n == -1)
2620 //fprintf(stderr, "selscroll to history\n");
2621 term->sel.by += n;
2622 term->sel.ey += n;
2623 //fprintf(stderr, " by=%d; ey=%d; maxhistory=%d\n", term->sel.by, term->sel.ey, term->maxhistory);
2624 if (term->sel.ey < 0 && -(term->sel.ey) > term->maxhistory) {
2625 // out of screen completely
2626 selclear(NULL);
2627 return;
2629 if (term->sel.by < 0 && -(term->sel.by) > term->maxhistory) {
2630 term->sel.by = -term->maxhistory;
2631 term->sel.bx = 0;
2632 docopy = 1;
2634 term->sel.b.x = term->sel.bx;
2635 term->sel.b.y = term->sel.by;
2636 term->sel.e.x = term->sel.ex;
2637 term->sel.e.y = term->sel.ey;
2640 if (docopy) selcopy();
2644 static void tscrolldown (int orig, int n) {
2645 Line temp;
2647 LIMIT(n, 0, term->bot-orig+1);
2648 if (n < 1) return;
2649 selscroll(orig, n, 0);
2650 //fprintf(stderr, "tscrolldown(%d, %d)\n", orig, n);
2651 tclearregion(0, term->bot-n+1, term->col-1, term->bot);
2652 for (int f = term->bot; f >= orig+n; --f) {
2653 temp = term->line[f];
2654 term->line[f] = term->line[f-n];
2655 term->line[f-n] = temp;
2656 markDirty(f, 2);
2657 markDirty(f-n, 2);
2662 static void tscrollup (int orig, int n, int tohistory) {
2663 Line temp;
2665 if (term == NULL) return;
2666 LIMIT(n, 0, term->bot-orig+1);
2667 if (n < 1) return;
2668 //fprintf(stderr, "tscrollup(%d, %d)\n", orig, n);
2669 if (tohistory && !IS_SET(MODE_ALTSCREEN) && term->maxhistory > 0) {
2670 Line l = term->line[term->linecount-1];
2672 for (int f = term->linecount-1; f > term->row; --f) term->line[f] = term->line[f-1];
2673 term->line[term->row] = l;
2674 for (int x = 0; x < term->col; ++x) l[x] = term->line[0][x];
2675 } else {
2676 tohistory = 0;
2679 selscroll(orig, -n, tohistory);
2680 //tclearregion(0, orig, term->col-1, orig+n-1);
2681 for (int f = orig; f <= term->bot-n; ++f) {
2682 temp = term->line[f];
2683 term->line[f] = term->line[f+n];
2684 term->line[f+n] = temp;
2685 markDirty(f, 2);
2686 markDirty(f+n, 2);
2688 tclearregion(0, term->bot-n+1, term->col-1, term->bot);
2692 static inline void tsetcharwrap (int y, int wrap) {
2693 if (y >= 0 && y < term->row) {
2694 if (wrap) term->line[y][term->col-1].state |= GLYPH_WRAP;
2695 else term->line[y][term->col-1].state &= ~GLYPH_WRAP;
2700 static void tnewline (int first_col) {
2701 int y = term->c.y;
2703 tsetcharwrap(y, (first_col == 2)); // 2: wrapping
2704 if (y == term->bot) tscrollup(term->top, 1, 1); else ++y;
2705 tmoveto(first_col ? 0 : term->c.x, y);
2709 static void csiparse (void) {
2710 const char *p = term->escseq.buf;
2712 term->escseq.narg = 0;
2713 if (*p == '?') { term->escseq.priv = 1; ++p; }
2714 while (p < term->escseq.buf+term->escseq.len) {
2715 int n = term->escseq.arg[term->escseq.narg];
2717 for (; *p && isdigit(*p); ++p) n = n*10+(p[0]-'0');
2718 term->escseq.arg[term->escseq.narg] = n;
2720 if (*p == ';' && term->escseq.narg+1 < ESC_ARG_SIZ) {
2721 ++term->escseq.narg;
2722 ++p;
2723 } else {
2724 term->escseq.mode = *p;
2725 ++term->escseq.narg;
2726 break;
2732 static void tsetchar (const char *c) {
2733 char ub[UTF_SIZ+1];
2734 int rev = 0, gfx = 0;
2735 int x = term->c.x, y = term->c.y;
2737 if (x < 0 || x >= term->col || y < 0 || y >= term->row) return;
2739 if (!term->needConv && unimap != NULL) {
2740 ulong cc;
2742 utf8decode(c, &cc);
2743 if (cc <= 65535) {
2744 ushort uc = unimap[cc];
2746 if (uc) {
2747 if (uc == 127) {
2748 // inversed space
2749 rev = 1;
2750 ub[0] = ' ';
2751 //ub[1] = 0;
2752 } else {
2753 if (uc&0x8000) {
2754 ub[0] = (uc&0x7f);
2755 gfx = 1;
2756 } else {
2757 //ub[0] = uc&0x7f;
2758 utf8encode(uc, ub);
2761 c = ub;
2765 markDirty(y, 1);
2767 term->line[y][x] = term->c.attr;
2768 if (rev) term->line[y][x].attr ^= ATTR_REVERSE;
2769 if (gfx || (term->mode&term->charset)) {
2770 term->line[y][x].attr |= ATTR_GFX;
2771 } else {
2772 term->line[y][x].attr &= ~ATTR_GFX;
2775 term->line[y][x].state = (GLYPH_SET | GLYPH_DIRTY);
2776 memmove(term->line[y][x].c, c, UTF_SIZ);
2778 if (IS_GFX(term->line[y][x].attr)) {
2779 unsigned char c = (unsigned char)(term->line[y][x].c[0]);
2781 if (c > 95 && c < 128) term->line[y][x].c[0] -= 95;
2782 else if (c > 127) term->line[y][x].c[0] = ' ';
2784 if (term->sel.bx != -1 && selected(x, y)) {
2785 selhide();
2787 //dlogf("tsetchar(%d,%d): [%c] (%d); dirty=%d\n", x, y, c[0], term->wantRedraw, term->dirty[y]);
2791 static void tdeletechar (int n) {
2792 int src = term->c.x+n;
2793 int dst = term->c.x;
2794 int size = term->col-src;
2796 markDirty(term->c.y, 2);
2797 if (src >= term->col) {
2798 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2799 } else {
2800 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2801 tclearregion(term->col-n, term->c.y, term->col-1, term->c.y);
2806 static void tinsertblank (int n) {
2807 int src = term->c.x;
2808 int dst = src+n;
2809 int size = term->col-dst;
2811 markDirty(term->c.y, 2);
2812 if (dst >= term->col) {
2813 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2814 } else {
2815 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2816 tclearregion(src, term->c.y, dst-1, term->c.y);
2821 static void tinsertblankline (int n) {
2822 if (term->c.y < term->top || term->c.y > term->bot) return;
2823 tscrolldown(term->c.y, n);
2827 static void tdeleteline (int n) {
2828 if (term->c.y < term->top || term->c.y > term->bot) return;
2829 tscrollup(term->c.y, n, 0);
2833 static void tsetattr (int *attr, int l) {
2834 for (int f = 0; f < l; ++f) {
2835 switch (attr[f]) {
2836 case 0:
2837 term->c.attr.attr &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
2838 term->c.attr.attr |= ATTR_DEFFG | ATTR_DEFBG;
2839 term->c.attr.fg = term->deffg;
2840 term->c.attr.bg = term->defbg;
2841 break;
2842 case 1:
2843 term->c.attr.attr |= ATTR_BOLD;
2844 break;
2845 case 4:
2846 term->c.attr.attr |= ATTR_UNDERLINE;
2847 break;
2848 case 7:
2849 term->c.attr.attr |= ATTR_REVERSE;
2850 break;
2851 case 22:
2852 term->c.attr.attr &= ~ATTR_BOLD;
2853 break;
2854 case 24:
2855 term->c.attr.attr &= ~ATTR_UNDERLINE;
2856 break;
2857 case 27:
2858 term->c.attr.attr &= ~ATTR_REVERSE;
2859 break;
2860 case 38:
2861 if (f+2 < l && attr[f+1] == 5) {
2862 f += 2;
2863 if (BETWEEN(attr[f], 0, 255)) {
2864 term->c.attr.fg = attr[f];
2865 term->c.attr.attr &= ~ATTR_DEFFG;
2866 } else {
2867 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[f]);
2869 } else {
2870 //fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2871 term->c.attr.fg = term->deffg;
2872 term->c.attr.attr |= ATTR_DEFFG;
2874 break;
2875 case 39:
2876 term->c.attr.fg = term->deffg;
2877 term->c.attr.attr |= ATTR_DEFFG;
2878 break;
2879 case 48:
2880 if (f+2 < l && attr[f+1] == 5) {
2881 f += 2;
2882 if (BETWEEN(attr[f], 0, 255)) {
2883 term->c.attr.bg = attr[f];
2884 term->c.attr.attr &= ~ATTR_DEFBG;
2885 } else {
2886 fprintf(stderr, "erresc: bad bgcolor %d\n", attr[f]);
2888 } else {
2889 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2891 break;
2892 case 49:
2893 term->c.attr.bg = term->defbg;
2894 term->c.attr.attr |= ATTR_DEFBG;
2895 break;
2896 default:
2897 if (BETWEEN(attr[f], 30, 37)) { term->c.attr.fg = attr[f]-30; term->c.attr.attr &= ~ATTR_DEFFG; }
2898 else if (BETWEEN(attr[f], 40, 47)) { term->c.attr.bg = attr[f]-40; term->c.attr.attr &= ~ATTR_DEFBG; }
2899 else if (BETWEEN(attr[f], 90, 97)) { term->c.attr.fg = attr[f]-90+8; term->c.attr.attr &= ~ATTR_DEFFG; }
2900 else if (BETWEEN(attr[f], 100, 107)) { term->c.attr.bg = attr[f]-100+8; term->c.attr.attr &= ~ATTR_DEFBG; }
2901 else { fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]); csidump(); }
2902 break;
2908 static void tsetscroll (int t, int b) {
2909 int temp;
2911 LIMIT(t, 0, term->row-1);
2912 LIMIT(b, 0, term->row-1);
2913 if (t > b) {
2914 temp = t;
2915 t = b;
2916 b = temp;
2918 term->top = t;
2919 term->bot = b;
2923 ////////////////////////////////////////////////////////////////////////////////
2924 // esc processing
2925 static void csihandle (void) {
2926 switch (term->escseq.mode) {
2927 case '@': /* ICH -- Insert <n> blank char */
2928 DEFAULT(term->escseq.arg[0], 1);
2929 tinsertblank(term->escseq.arg[0]);
2930 break;
2931 case 'A': /* CUU -- Cursor <n> Up */
2932 case 'e':
2933 DEFAULT(term->escseq.arg[0], 1);
2934 tmoveto(term->c.x, term->c.y-term->escseq.arg[0]);
2935 break;
2936 case 'B': /* CUD -- Cursor <n> Down */
2937 DEFAULT(term->escseq.arg[0], 1);
2938 tmoveto(term->c.x, term->c.y+term->escseq.arg[0]);
2939 break;
2940 case 'C': /* CUF -- Cursor <n> Forward */
2941 case 'a':
2942 DEFAULT(term->escseq.arg[0], 1);
2943 tmoveto(term->c.x+term->escseq.arg[0], term->c.y);
2944 break;
2945 case 'D': /* CUB -- Cursor <n> Backward */
2946 DEFAULT(term->escseq.arg[0], 1);
2947 tmoveto(term->c.x-term->escseq.arg[0], term->c.y);
2948 break;
2949 case 'E': /* CNL -- Cursor <n> Down and first col */
2950 DEFAULT(term->escseq.arg[0], 1);
2951 tmoveto(0, term->c.y+term->escseq.arg[0]);
2952 break;
2953 case 'F': /* CPL -- Cursor <n> Up and first col */
2954 DEFAULT(term->escseq.arg[0], 1);
2955 tmoveto(0, term->c.y-term->escseq.arg[0]);
2956 break;
2957 case 'G': /* CHA -- Move to <col> */
2958 case '`': /* XXX: HPA -- same? */
2959 DEFAULT(term->escseq.arg[0], 1);
2960 tmoveto(term->escseq.arg[0]-1, term->c.y);
2961 break;
2962 case 'H': /* CUP -- Move to <row> <col> */
2963 case 'f': /* XXX: HVP -- same? */
2964 DEFAULT(term->escseq.arg[0], 1);
2965 DEFAULT(term->escseq.arg[1], 1);
2966 tmoveto(term->escseq.arg[1]-1, term->escseq.arg[0]-1);
2967 break;
2968 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
2969 case 'J': /* ED -- Clear screen */
2970 term->sel.bx = -1;
2971 switch (term->escseq.arg[0]) {
2972 case 0: /* below */
2973 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2974 if (term->c.y < term->row-1) tclearregion(0, term->c.y+1, term->col-1, term->row-1);
2975 break;
2976 case 1: /* above */
2977 if (term->c.y > 1) tclearregion(0, 0, term->col-1, term->c.y-1);
2978 tclearregion(0, term->c.y, term->c.x, term->c.y);
2979 break;
2980 case 2: /* all */
2981 tclearregion(0, 0, term->col-1, term->row-1);
2982 break;
2983 default:
2984 goto unknown;
2986 break;
2987 case 'K': /* EL -- Clear line */
2988 switch (term->escseq.arg[0]) {
2989 case 0: /* right */
2990 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2991 break;
2992 case 1: /* left */
2993 tclearregion(0, term->c.y, term->c.x, term->c.y);
2994 break;
2995 case 2: /* all */
2996 tclearregion(0, term->c.y, term->col-1, term->c.y);
2997 break;
2999 break;
3000 case 'S': /* SU -- Scroll <n> line up */
3001 DEFAULT(term->escseq.arg[0], 1);
3002 tscrollup(term->top, term->escseq.arg[0], 0);
3003 break;
3004 case 'T': /* SD -- Scroll <n> line down */
3005 DEFAULT(term->escseq.arg[0], 1);
3006 tscrolldown(term->top, term->escseq.arg[0]);
3007 break;
3008 case 'L': /* IL -- Insert <n> blank lines */
3009 DEFAULT(term->escseq.arg[0], 1);
3010 tinsertblankline(term->escseq.arg[0]);
3011 break;
3012 case 'l': /* RM -- Reset Mode */
3013 if (term->escseq.priv) {
3014 switch (term->escseq.arg[0]) {
3015 case 1: // 1001 for xterm compatibility
3016 DUMP_KEYPAD_SWITCH("1", "OFF");
3017 term->mode &= ~MODE_APPKEYPAD;
3018 break;
3019 case 5: /* DECSCNM -- Remove reverse video */
3020 if (IS_SET(MODE_REVERSE)) {
3021 term->mode &= ~MODE_REVERSE;
3022 tfulldirt();
3024 break;
3025 case 7: /* autowrap off */
3026 term->mode &= ~MODE_WRAP;
3027 break;
3028 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
3029 break;
3030 case 20: /* non-standard code? */
3031 term->mode &= ~MODE_CRLF;
3032 break;
3033 case 25: /* hide cursor */
3034 if ((term->c.state&CURSOR_HIDE) == 0) {
3035 term->c.state |= CURSOR_HIDE;
3036 term->wantRedraw = 1;
3038 break;
3039 case 1000: /* disable X11 xterm mouse reporting */
3040 term->mode &= ~MODE_MOUSEBTN;
3041 break;
3042 case 1002:
3043 term->mode &= ~MODE_MOUSEMOTION;
3044 break;
3045 case 1004:
3046 term->mode &= ~MODE_FOCUSEVT;
3047 break;
3048 case 1005: /* utf-8 mouse encoding */
3049 case 1006: /* sgr mouse encoding */
3050 case 1015: /* urxvt mouse encoding */
3051 term->mousemode = 1000;
3052 break;
3053 case 1049: /* = 1047 and 1048 */
3054 case 47:
3055 case 1047:
3056 if (IS_SET(MODE_ALTSCREEN)) {
3057 tclearregion(0, 0, term->col-1, term->row-1);
3058 tswapscreen();
3060 if (term->escseq.arg[0] != 1049) break;
3061 case 1048:
3062 tcursor(CURSOR_LOAD);
3063 break;
3064 case 2004: /* reset bracketed paste mode */
3065 term->mode &= ~MODE_BRACPASTE;
3066 break;
3067 default:
3068 goto unknown;
3070 } else {
3071 switch (term->escseq.arg[0]) {
3072 case 3:
3073 term->mode &= ~MODE_DISPCTRL;
3074 break;
3075 case 4:
3076 term->mode &= ~MODE_INSERT;
3077 break;
3078 default:
3079 goto unknown;
3082 break;
3083 case 'M': /* DL -- Delete <n> lines */
3084 DEFAULT(term->escseq.arg[0], 1);
3085 tdeleteline(term->escseq.arg[0]);
3086 break;
3087 case 'X': /* ECH -- Erase <n> char */
3088 DEFAULT(term->escseq.arg[0], 1);
3089 tclearregion(term->c.x, term->c.y, term->c.x + term->escseq.arg[0], term->c.y);
3090 break;
3091 case 'P': /* DCH -- Delete <n> char */
3092 DEFAULT(term->escseq.arg[0], 1);
3093 tdeletechar(term->escseq.arg[0]);
3094 break;
3095 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
3096 case 'd': /* VPA -- Move to <row> */
3097 DEFAULT(term->escseq.arg[0], 1);
3098 tmoveto(term->c.x, term->escseq.arg[0]-1);
3099 break;
3100 case 'h': /* SM -- Set terminal mode */
3101 if (term->escseq.priv) {
3102 switch (term->escseq.arg[0]) {
3103 case 1:
3104 DUMP_KEYPAD_SWITCH("1", "ON");
3105 term->mode |= MODE_APPKEYPAD;
3106 break;
3107 case 5: /* DECSCNM -- Reverve video */
3108 if (!IS_SET(MODE_REVERSE)) {
3109 term->mode |= MODE_REVERSE;
3110 tfulldirt();
3112 break;
3113 case 7:
3114 term->mode |= MODE_WRAP;
3115 break;
3116 case 20:
3117 term->mode |= MODE_CRLF;
3118 break;
3119 case 12: /* att610 -- Start blinking cursor (IGNORED) */
3120 /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
3121 if (term->escseq.narg > 1 && term->escseq.arg[1] != 25) break;
3122 case 25:
3123 if ((term->c.state&CURSOR_HIDE) != 0) {
3124 term->c.state &= ~CURSOR_HIDE;
3125 term->wantRedraw = 1;
3127 break;
3128 case 1000: /* 1000,1002: enable xterm mouse report */
3129 term->mode |= MODE_MOUSEBTN;
3130 break;
3131 case 1002:
3132 term->mode |= MODE_MOUSEMOTION;
3133 break;
3134 case 1004:
3135 term->mode |= MODE_FOCUSEVT;
3136 break;
3137 case 1005: /* utf-8 mouse encoding */
3138 case 1006: /* sgr mouse encoding */
3139 case 1015: /* urxvt mouse encoding */
3140 term->mousemode = term->escseq.arg[0];
3141 break;
3142 case 1049: /* = 1047 and 1048 */
3143 case 47:
3144 case 1047:
3145 if (IS_SET(MODE_ALTSCREEN)) tclearregion(0, 0, term->col-1, term->row-1); else tswapscreen();
3146 if (term->escseq.arg[0] != 1049) break;
3147 case 1048:
3148 tcursor(CURSOR_SAVE);
3149 break;
3150 case 2004: /* set bracketed paste mode */
3151 term->mode |= MODE_BRACPASTE;
3152 break;
3153 default: goto unknown;
3155 } else {
3156 switch (term->escseq.arg[0]) {
3157 case 3:
3158 term->mode |= MODE_DISPCTRL;
3159 break;
3160 case 4:
3161 term->mode |= MODE_INSERT;
3162 break;
3163 default:
3164 goto unknown;
3167 break;
3168 case 'm': /* SGR -- Terminal attribute (color) */
3169 tsetattr(term->escseq.arg, term->escseq.narg);
3170 break;
3171 case 'n':
3172 if (!term->escseq.priv) {
3173 switch (term->escseq.arg[0]) {
3174 case 6: { /* cursor position report */
3175 char buf[32];
3177 sprintf(buf, "\x1b[%d;%dR", term->c.x+1, term->c.y+1);
3178 ttywritestr(buf);
3179 } break;
3182 break;
3183 case 'r': /* DECSTBM -- Set Scrolling Region */
3184 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
3185 // xterm compatibility
3186 DUMP_KEYPAD_SWITCH("1001", "OFF");
3187 term->mode &= ~MODE_APPKEYPAD;
3188 } else if (term->escseq.priv) {
3189 goto unknown;
3190 } else {
3191 DEFAULT(term->escseq.arg[0], 1);
3192 DEFAULT(term->escseq.arg[1], term->row);
3193 tsetscroll(term->escseq.arg[0]-1, term->escseq.arg[1]-1);
3194 tmoveto(0, 0);
3196 break;
3197 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
3198 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
3199 // xterm compatibility
3200 DUMP_KEYPAD_SWITCH("1001", "ON");
3201 term->mode |= MODE_APPKEYPAD;
3202 } else {
3203 tcursor(CURSOR_SAVE);
3205 break;
3206 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
3207 tcursor(CURSOR_LOAD);
3208 break;
3209 default:
3210 unknown:
3211 fprintf(stderr, "erresc: unknown csi ");
3212 csidump();
3213 break;
3218 static void csireset (void) {
3219 memset(&term->escseq, 0, sizeof(term->escseq));
3223 static void tputtab (void) {
3224 int space = opt_tabsize-term->c.x%opt_tabsize;
3226 if (space > 0) tmoveto(term->c.x+space, term->c.y);
3230 ////////////////////////////////////////////////////////////////////////////////
3231 // put char to output buffer or process command
3233 // return 1 if this was control character
3234 // return -1 if this should break esape sequence
3235 static int tputc_ctrl (char ascii) {
3236 int res = 1;
3238 if (term->esc&ESC_TITLE) return 0;
3240 switch (ascii) {
3241 case '\t': tputtab(); break;
3242 case '\b': tmoveto(term->c.x-1, term->c.y); break;
3243 case '\r': tmoveto(0, term->c.y); break;
3244 case '\f': case '\n': case '\v': tnewline(IS_SET(MODE_CRLF)?1:0); break; /* go to first col if the mode is set */
3245 case '\a':
3246 if (!(xw.state & WIN_FOCUSED) && (term->belltype&BELL_URGENT)) xseturgency(1);
3247 if (term->belltype&BELL_AUDIO) XBell(xw.dpy, 100);
3248 break;
3249 case 14: term->charset = MODE_GFX1; break;
3250 case 15: term->charset = MODE_GFX0; break;
3251 case 0x18: case 0x1a: res = -1; break; // do nothing, interrupt current escape sequence
3252 case 127: break; // ignore it
3253 case '\033': csireset(); term->esc = ESC_START; break;
3254 //case 0x9b: csireset(); term->esc = ESC_START | ESC_CSI; break;
3255 default: res = 0; break;
3257 return res;
3261 static void tputc (const char *c) {
3262 char ascii = *c;
3263 int ctl = tputc_ctrl(ascii);
3265 if (ctl > 0) return; // control char; should not break escape sequence
3266 if (ctl < 0) {
3267 // control char; should break escape sequence
3268 term->esc = 0;
3269 return;
3271 //dlogf("tputc: [%c]\n", c[0]);
3272 if (term->esc & ESC_START) {
3273 if (term->esc & ESC_CSI) {
3274 term->escseq.buf[term->escseq.len++] = ascii;
3275 if (BETWEEN(ascii, 0x40, 0x7E) || term->escseq.len >= ESC_BUF_SIZ) {
3276 term->esc = 0;
3277 csiparse();
3278 csihandle();
3280 } else if (term->esc & ESC_OSC) {
3281 /* TODO: handle other OSC */
3282 if (ascii == ';') {
3283 term->title[0] = 0;
3284 term->titlelen = 0;
3285 term->esc = ESC_START | ESC_TITLE;
3286 //updateTabBar = 1;
3288 } else if (term->esc & ESC_TITLE) {
3289 int len = utf8size(c);
3291 if (ascii == '\a' || term->titlelen+len >= ESC_TITLE_SIZ) {
3292 term->esc = 0;
3293 term->title[term->titlelen] = '\0';
3294 fixWindowTitle(term);
3295 updateTabBar = 1;
3296 } else if (len > 0) {
3297 memcpy(term->title+term->titlelen, c, len);
3298 term->titlelen += len;
3299 term->title[term->titlelen] = '\0';
3301 } else if (term->esc & ESC_ALTCHARSET) {
3302 term->esc = 0;
3303 switch (ascii) {
3304 case '0': /* Line drawing crap */
3305 term->mode |= MODE_GFX0;
3306 break;
3307 case 'B': /* Back to regular text */
3308 term->mode &= ~MODE_GFX0;
3309 break;
3310 default:
3311 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
3312 term->mode &= ~MODE_GFX0;
3313 break;
3315 } else if (term->esc & ESC_ALTG1) {
3316 term->esc = 0;
3317 switch (ascii) {
3318 case '0': /* Line drawing crap */
3319 term->mode |= MODE_GFX1;
3320 break;
3321 case 'B': /* Back to regular text */
3322 term->mode &= ~MODE_GFX1;
3323 break;
3324 default:
3325 fprintf(stderr, "esc unhandled charset: ESC ) %c\n", ascii);
3326 term->mode &= ~MODE_GFX1;
3327 break;
3329 } else if (term->esc & ESC_HASH) {
3330 term->esc = 0;
3331 switch (ascii) {
3332 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
3333 //tfillscreenwithE();
3334 break;
3336 } else if (term->esc & ESC_PERCENT) {
3337 term->esc = 0;
3338 } else {
3339 switch (ascii) {
3340 case '[': term->esc |= ESC_CSI; break;
3341 case ']': term->esc |= ESC_OSC; break;
3342 case '(': term->esc |= ESC_ALTCHARSET; break;
3343 case ')': term->esc |= ESC_ALTG1; break;
3344 case '#': term->esc |= ESC_HASH; break;
3345 case '%': term->esc |= ESC_PERCENT; break;
3346 case 'D': /* IND -- Linefeed */
3347 term->esc = 0;
3348 if (term->c.y == term->bot) tscrollup(term->top, 1, 1); else tmoveto(term->c.x, term->c.y+1);
3349 break;
3350 case 'E': /* NEL -- Next line */
3351 term->esc = 0;
3352 tnewline(1); /* always go to first col */
3353 break;
3354 case 'M': /* RI -- Reverse linefeed */
3355 term->esc = 0;
3356 if (term->c.y == term->top) tscrolldown(term->top, 1); else tmoveto(term->c.x, term->c.y-1);
3357 break;
3358 case 'c': /* RIS -- Reset to inital state */
3359 term->esc = 0;
3360 treset();
3361 break;
3362 case '=': /* DECPAM -- Application keypad */
3363 DUMP_KEYPAD_SWITCH("=", "ON");
3364 term->esc = 0;
3365 term->mode |= MODE_APPKEYPAD;
3366 break;
3367 case '>': /* DECPNM -- Normal keypad */
3368 DUMP_KEYPAD_SWITCH(">", "OFF");
3369 term->esc = 0;
3370 term->mode &= ~MODE_APPKEYPAD;
3371 break;
3372 case '7': /* DECSC -- Save Cursor */
3373 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
3374 //TODO?
3375 term->esc = 0;
3376 tcursor(CURSOR_SAVE);
3377 break;
3378 case '8': /* DECRC -- Restore Cursor */
3379 //TODO?
3380 term->esc = 0;
3381 tcursor(CURSOR_LOAD);
3382 break;
3383 case 'Z': /* DEC private identification */
3384 term->esc = 0;
3385 ttywritestr("\x1b[?1;2c");
3386 break;
3387 default:
3388 term->esc = 0;
3389 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X ('%c')\n", (uchar)ascii, isprint(ascii)?ascii:'.');
3390 break;
3393 } else {
3394 //if (term->sel.bx != -1 && BETWEEN(term->c.y, term->sel.by, term->sel.ey)) term->sel.bx = -1;
3395 do {
3396 if (term->needConv && IS_GFX(term->c.attr.attr)) {
3397 ulong cc;
3399 utf8decode(c, &cc);
3400 if (cc < 32 || cc >= 127) break; //FIXME: nothing at all?
3401 } else {
3402 if ((unsigned char)ascii < 32 || ascii == 127) break; // seems that this chars are empty too
3404 if (term->c.state&CURSOR_WRAPNEXT) {
3405 if (IS_SET(MODE_WRAP)) {
3406 // always go to first col
3407 tnewline(2);
3408 } else {
3409 tsetcharwrap(term->c.y, 0);
3410 break; // wrap is off, don't want more chars
3413 tsetchar(c);
3414 if (term->c.x+1 < term->col) tmoveto(term->c.x+1, term->c.y); else term->c.state |= CURSOR_WRAPNEXT;
3415 } while (0);
3420 static void tunshowhistory (void) {
3421 if (term != NULL && term->topline != 0) {
3422 term->topline = 0;
3423 term->wantRedraw = 1;
3424 term->lastDrawTime = 0;
3429 static void tsendfocusevent (int focused) {
3430 if (term != NULL && IS_SET(MODE_FOCUSEVT)) {
3431 ttywritestr("\x1b[");
3432 ttywrite(focused?"I":"O", 1);
3437 static void tcmdlinedirty (void) {
3438 if (term != NULL) {
3439 markDirty(term->row-term->topline-1, 2);
3440 term->wantRedraw = 1;
3445 static void tcmdlinefixofs (void) {
3446 int ofs, len;
3448 len = utf8strlen(term->cmdline);
3449 ofs = len-(term->col-1);
3450 if (ofs < 0) ofs = 0;
3451 for (term->cmdofs = 0; ofs > 0; --ofs) term->cmdofs += utf8size(term->cmdline+term->cmdofs);
3452 tcmdlinedirty();
3456 static void tcmdlinehide (void) {
3457 term->cmdMode = CMDMODE_NONE;
3458 term->cmdprevc = NULL;
3459 tcmdlinedirty();
3463 // utf-8
3464 static void tcmdlinemsg (const char *msg) {
3465 if (msg != NULL) {
3466 int ofs = 0;
3468 term->cmdMode = CMDMODE_MESSAGE;
3469 term->cmdofs = 0;
3470 term->cmdtabpos = -1;
3471 term->cmdprevc = NULL;
3473 while (*msg) {
3474 int len = utf8size(msg);
3476 if (len < 1 || ofs+len >= sizeof(term->cmdline)-1) break;
3477 memcpy(term->cmdline+ofs, msg, len);
3478 ofs += len;
3479 msg += len;
3482 term->cmdline[ofs] = 0;
3483 tcmdlinedirty();
3488 static void tcmdlineinitex (const char *msg) {
3489 term->cmdMode = CMDMODE_INPUT;
3490 term->cmdofs = 0;
3491 term->cmdline[0] = 0;
3492 term->cmdc[0] = 0;
3493 term->cmdcl = 0;
3494 term->cmdtabpos = -1;
3495 term->cmdprevc = NULL;
3496 term->cmdreslen = 0;
3497 term->cmdexecfn = NULL;
3498 if (msg != NULL && msg[0]) {
3499 strcpy(term->cmdline, msg);
3500 term->cmdreslen = strlen(term->cmdline);
3502 tcmdlinefixofs();
3506 static void tcmdlineinit (void) {
3507 tcmdlineinitex(NULL);
3511 static void tcmdlinechoplast (void) {
3512 if (term->cmdcl != 0) {
3513 term->cmdcl = 0;
3514 } else {
3515 if (strlen(term->cmdline) > term->cmdreslen) utf8choplast(term->cmdline);
3517 tcmdlinefixofs();
3521 // utf-8
3522 static void tcmdaddchar (const char *s) {
3523 int len = utf8size(s);
3525 if (len > 0) {
3526 int slen = strlen(term->cmdline);
3528 if (slen+len < sizeof(term->cmdline)) {
3529 memcpy(term->cmdline+slen, s, len);
3530 term->cmdline[slen+len] = 0;
3531 tcmdlinefixofs();
3537 static void tcmdput (const char *s, int len) {
3538 while (len-- > 0) {
3539 int ok;
3541 term->cmdc[term->cmdcl++] = *s++;
3542 term->cmdc[term->cmdcl] = 0;
3544 if ((ok = isfullutf8(term->cmdc, term->cmdcl)) != 0 || term->cmdcl == UTF_SIZ) {
3545 if (ok) tcmdaddchar(term->cmdc);
3546 term->cmdcl = 0;
3552 ////////////////////////////////////////////////////////////////////////////////
3553 // tty resising
3554 static int tresize (int col, int row) {
3555 int mincol = MIN(col, term->col);
3556 int slide = term->c.y-row+1;
3557 Glyph g;
3559 if (col < 1 || row < 1) return 0;
3561 selhide();
3563 g.state = GLYPH_DIRTY;
3564 g.attr = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG;
3565 g.fg = term->deffg;
3566 g.bg = term->defbg;
3567 g.c[0] = ' ';
3568 g.c[1] = 0;
3570 if (slide > 0) {
3571 tsetscroll(0, term->row-1);
3572 for (; slide > 0; --slide) tscrollup(0, 1, 1); // to fill history
3575 if (row < term->row) {
3576 /* free unneeded rows */
3577 for (int f = row; f < term->row; ++f) free(term->alt[f]);
3578 for (int f = term->linecount-(term->row-row); f < term->linecount; ++f) free(term->line[f]);
3579 term->linecount -= (term->row-row);
3580 /* resize to new height */
3581 term->alt = realloc(term->alt, row*sizeof(Line));
3582 term->line = realloc(term->line, term->linecount*sizeof(Line));
3583 } else if (row > term->row) {
3584 /* resize to new height */
3585 term->alt = realloc(term->alt, row*sizeof(Line));
3586 term->line = realloc(term->line, (row+term->maxhistory)*sizeof(Line));
3587 /* add more lines */
3588 for (int f = term->row; f < row; ++f) {
3589 term->alt[f] = calloc(col, sizeof(Glyph));
3590 for (int x = 0; x < col; ++x) term->alt[f][x] = g;
3592 for (int f = 0; f < row-term->row; ++f) {
3593 int y = term->linecount++;
3595 term->line[y] = calloc(col, sizeof(Glyph));
3596 for (int x = 0; x < col; ++x) term->line[y][x] = g;
3600 if (row != term->row) {
3601 term->dirty = realloc(term->dirty, row*sizeof(*term->dirty));
3604 /* resize each row to new width, zero-pad if needed */
3605 for (int f = 0; f < term->linecount; ++f) {
3606 term->line[f] = realloc(term->line[f], col*sizeof(Glyph));
3607 for (int x = mincol; x < col; ++x) term->line[f][x] = g;
3608 if (f < row) {
3609 markDirty(f, 2);
3610 term->alt[f] = realloc(term->alt[f], col*sizeof(Glyph));
3611 for (int x = mincol; x < col; ++x) term->alt[f][x] = g;
3614 /* update terminal size */
3615 term->topline = 0;
3616 term->col = col;
3617 term->row = row;
3618 /* make use of the LIMIT in tmoveto */
3619 tmoveto(term->c.x, term->c.y);
3620 /* reset scrolling region */
3621 tsetscroll(0, row-1);
3622 tfulldirt();
3623 return (slide > 0);
3627 static void xresize (int col, int row) {
3628 Pixmap newbuf;
3629 int oldw, oldh;
3631 if (term == NULL) return;
3632 oldw = term->picbufw;
3633 oldh = term->picbufh;
3634 term->picbufw = MAX(1, col*xw.cw);
3635 term->picbufh = MAX(1, row*xw.ch);
3636 newbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3637 XCopyArea(xw.dpy, term->picbuf, newbuf, dc.gc, 0, 0, term->picbufw, term->picbufh, 0, 0);
3638 XFreePixmap(xw.dpy, term->picbuf);
3639 XSetForeground(xw.dpy, dc.gc, getColor(term->defbg));
3640 if (term->picbufw > oldw) {
3641 XFillRectangle(xw.dpy, newbuf, dc.gc, oldw, 0, term->picbufw-oldw, MIN(term->picbufh, oldh));
3642 } else if (term->picbufw < oldw && xw.w > term->picbufw) {
3643 XClearArea(xw.dpy, xw.win, term->picbufw, 0, xw.w-term->picbufh, MIN(term->picbufh, oldh), False);
3645 if (term->picbufh > oldh) {
3646 XFillRectangle(xw.dpy, newbuf, dc.gc, 0, oldh, term->picbufw, term->picbufh-oldh);
3647 } else if (term->picbufh < oldh && xw.h > term->picbufh) {
3648 XClearArea(xw.dpy, xw.win, 0, term->picbufh, xw.w, xw.h-term->picbufh, False);
3650 term->picbuf = newbuf;
3651 tfulldirt();
3652 updateTabBar = 1;
3656 ////////////////////////////////////////////////////////////////////////////////
3657 // x11 drawing and utils
3659 static void xcreatebw (void) {
3660 if ((dc.bcol = calloc(MAX_COLOR+1, sizeof(dc.bcol[0]))) == NULL) die("out of memory");
3662 for (int f = 0; f <= MAX_COLOR; ++f) {
3663 XColor nclr;
3665 nclr = dc.ncol[f].pixel;
3666 XQueryColor(xw.dpy, xw.cmap, &nclr);
3667 fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", f, nclr.red, nclr.green, nclr.blue);
3673 static void xallocbwclr (int idx, XColor *color) {
3674 double lumi;
3676 XQueryColor(xw.dpy, xw.cmap, color);
3677 //fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", idx, color->red, color->green, color->blue);
3679 lumi = 0.3*((double)color->red/65535.0)+0.59*((double)color->green/65535.0)+0.11*((double)color->blue/65535.0);
3680 color->red = color->green = color->blue = (int)(lumi*65535.0);
3681 if (!XAllocColor(xw.dpy, xw.cmap, color)) {
3682 fprintf(stderr, "WARNING: could not allocate b/w color #%d\n", idx);
3683 return;
3685 dc.bcol[idx] = color->pixel;
3686 color->red = color->blue = 0;
3687 if (!XAllocColor(xw.dpy, xw.cmap, color)) {
3688 fprintf(stderr, "WARNING: could not allocate b/w color #%d\n", idx);
3689 return;
3691 dc.gcol[idx] = color->pixel;
3695 static void xallocnamedclr (int idx, const char *cname) {
3696 XColor color;
3698 if (!XAllocNamedColor(xw.dpy, xw.cmap, cname, &color, &color)) {
3699 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", idx, cname);
3700 return;
3702 dc.ncol[idx] = color.pixel;
3703 xallocbwclr(idx, &color);
3707 static void xloadcols (void) {
3708 int f, r, g, b;
3709 XColor color;
3710 ulong white = WhitePixel(xw.dpy, xw.scr);
3712 if ((dc.clrs[0] = dc.ncol = calloc(MAX_COLOR+1, sizeof(dc.ncol[0]))) == NULL) die("out of memory");
3713 if ((dc.clrs[1] = dc.bcol = calloc(MAX_COLOR+1, sizeof(dc.bcol[0]))) == NULL) die("out of memory");
3714 if ((dc.clrs[2] = dc.gcol = calloc(MAX_COLOR+1, sizeof(dc.gcol[0]))) == NULL) die("out of memory");
3716 for (f = 0; f <= MAX_COLOR; ++f) dc.ncol[f] = dc.bcol[f] = white;
3717 /* load colors [0-15] */
3718 for (f = 0; f <= 15; ++f) {
3719 const char *cname = opt_colornames[f]!=NULL?opt_colornames[f]:defcolornames[f];
3721 xallocnamedclr(f, cname);
3723 /* load colors [256-...] */
3724 for (f = 256; f <= MAX_COLOR; ++f) {
3725 const char *cname = opt_colornames[f];
3727 if (cname == NULL) {
3728 if (LEN(defextcolornames) <= f-256) continue;
3729 cname = defextcolornames[f-256];
3731 if (cname == NULL) continue;
3732 xallocnamedclr(f, cname);
3734 /* load colors [16-255] ; same colors as xterm */
3735 for (f = 16, r = 0; r < 6; ++r) {
3736 for (g = 0; g < 6; ++g) {
3737 for (b = 0; b < 6; ++b) {
3738 if (opt_colornames[f] != NULL) {
3739 xallocnamedclr(f, opt_colornames[f]);
3740 } else {
3741 color.red = r == 0 ? 0 : 0x3737+0x2828*r;
3742 color.green = g == 0 ? 0 : 0x3737+0x2828*g;
3743 color.blue = b == 0 ? 0 : 0x3737+0x2828*b;
3744 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3745 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3746 } else {
3747 dc.ncol[f] = color.pixel;
3748 xallocbwclr(f, &color);
3751 ++f;
3755 for (r = 0; r < 24; ++r, ++f) {
3756 if (opt_colornames[f] != NULL) {
3757 xallocnamedclr(f, opt_colornames[f]);
3758 } else {
3759 color.red = color.green = color.blue = 0x0808+0x0a0a*r;
3760 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3761 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3762 } else {
3763 dc.ncol[f] = color.pixel;
3764 xallocbwclr(f, &color);
3769 for (int f = 0; f < LEN(opt_colornames); ++f) if (opt_colornames[f]) free(opt_colornames[f]);
3773 static void xclear (int x1, int y1, int x2, int y2) {
3774 XSetForeground(xw.dpy, dc.gc, getColor(IS_SET(MODE_REVERSE) ? term->deffg : term->defbg));
3775 XFillRectangle(xw.dpy, term->picbuf, dc.gc, x1*xw.cw, y1*xw.ch, (x2-x1+1)*xw.cw, (y2-y1+1)*xw.ch);
3779 static void xhints (void) {
3780 XClassHint class = {opt_class, opt_title};
3781 XWMHints wm = {.flags = InputHint, .input = 1};
3782 XSizeHints size = {
3783 .flags = PSize | PResizeInc | PBaseSize,
3784 .height = xw.h,
3785 .width = xw.w,
3786 .height_inc = xw.ch,
3787 .width_inc = xw.cw,
3788 .base_height = xw.h/*xw.tabheight*/,
3789 .base_width = xw.w,
3791 //XSetWMNormalHints(xw.dpy, xw.win, &size);
3792 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, &size, &wm, &class);
3793 XSetWMProtocols(xw.dpy, xw.win, &XA_WM_DELETE_WINDOW, 1);
3797 static XFontSet xinitfont (const char *fontstr) {
3798 XFontSet set;
3799 char *def, **missing;
3800 int n;
3802 missing = NULL;
3803 set = XCreateFontSet(xw.dpy, fontstr, &missing, &n, &def);
3804 if (missing) {
3805 while (n--) fprintf(stderr, "sterm: missing fontset: %s\n", missing[n]);
3806 XFreeStringList(missing);
3808 return set;
3812 static void xgetfontinfo (XFontSet set, int *ascent, int *descent, short *lbearing, short *rbearing, Font *fid) {
3813 XFontStruct **xfonts;
3814 char **font_names;
3815 int n;
3817 *ascent = *descent = *lbearing = *rbearing = 0;
3818 n = XFontsOfFontSet(set, &xfonts, &font_names);
3819 for (int f = 0; f < n; ++f) {
3820 if (f == 0) *fid = (*xfonts)->fid;
3821 *ascent = MAX(*ascent, (*xfonts)->ascent);
3822 *descent = MAX(*descent, (*xfonts)->descent);
3823 *lbearing = MAX(*lbearing, (*xfonts)->min_bounds.lbearing);
3824 *rbearing = MAX(*rbearing, (*xfonts)->max_bounds.rbearing);
3825 ++xfonts;
3830 static void initfonts (const char *fontstr, const char *bfontstr, const char *tabfont) {
3831 if ((dc.font[0].set = xinitfont(fontstr)) == NULL) {
3832 if ((dc.font[0].set = xinitfont(FONT)) == NULL) die("can't load font %s", fontstr);
3834 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);
3836 if ((dc.font[1].set = xinitfont(bfontstr)) == NULL) {
3837 if ((dc.font[1].set = xinitfont(FONTBOLD)) == NULL) die("can't load font %s", bfontstr);
3839 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);
3841 if ((dc.font[2].set = xinitfont(tabfont)) == NULL) {
3842 if ((dc.font[2].set = xinitfont(FONTTAB)) == NULL) die("can't load font %s", tabfont);
3844 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);
3848 static void xinit (void) {
3849 XSetWindowAttributes attrs;
3850 Window parent;
3851 XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
3853 if (!(xw.dpy = XOpenDisplay(NULL))) die("can't open display");
3855 XA_VT_SELECTION = XInternAtom(xw.dpy, "_STERM_SELECTION_", 0);
3856 XA_CLIPBOARD = XInternAtom(xw.dpy, "CLIPBOARD", 0);
3857 XA_UTF8 = XInternAtom(xw.dpy, "UTF8_STRING", 0);
3858 XA_NETWM_NAME = XInternAtom(xw.dpy, "_NET_WM_NAME", 0);
3859 XA_TARGETS = XInternAtom(xw.dpy, "TARGETS", 0);
3860 XA_WM_DELETE_WINDOW = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", 0);
3861 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
3863 xw.scr = XDefaultScreen(xw.dpy);
3864 /* font */
3865 initfonts(opt_fontnorm, opt_fontbold, opt_fonttab);
3866 /* XXX: Assuming same size for bold font */
3867 xw.cw = dc.font[0].rbearing-dc.font[0].lbearing;
3868 xw.ch = dc.font[0].ascent+dc.font[0].descent;
3869 xw.tch = dc.font[2].ascent+dc.font[2].descent;
3870 xw.tabheight = opt_disabletabs ? 0 : xw.tch+2;
3871 //fprintf(stderr, "tabh: %d\n", xw.tabheight);
3872 //xw.tabheight = 0;
3873 /* colors */
3874 xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
3875 xloadcols();
3876 /* window - default size */
3877 term->picbufh = term->row*xw.ch;
3878 term->picbufw = term->col*xw.cw;
3880 xw.h = term->picbufh+xw.tabheight;
3881 xw.w = term->picbufw;
3883 attrs.background_pixel = getColor(defaultBG);
3884 attrs.border_pixel = getColor(defaultBG);
3885 attrs.bit_gravity = NorthWestGravity;
3886 attrs.event_mask = FocusChangeMask | KeyPressMask
3887 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
3888 | /*ButtonMotionMask*/ PointerMotionMask | ButtonPressMask | ButtonReleaseMask
3889 | EnterWindowMask | LeaveWindowMask;
3890 attrs.colormap = xw.cmap;
3891 //fprintf(stderr, "oe: [%s]\n", opt_embed);
3892 parent = opt_embed ? strtol(opt_embed, NULL, 0) : XRootWindow(xw.dpy, xw.scr);
3893 xw.win = XCreateWindow(xw.dpy, parent, 0, 0,
3894 xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
3895 XDefaultVisual(xw.dpy, xw.scr),
3896 CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask
3897 | CWColormap,
3898 &attrs);
3899 xhints();
3900 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3901 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
3902 /* input methods */
3903 if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) die("XOpenIM() failed");
3904 xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL);
3905 /* gc */
3906 dc.gc = XCreateGC(xw.dpy, xw.win, 0, NULL);
3907 /* white cursor, black outline */
3908 xw.cursor = XCreateFontCursor(xw.dpy, XC_xterm);
3909 XDefineCursor(xw.dpy, xw.win, xw.cursor);
3910 XRecolorCursor(xw.dpy, xw.cursor,
3911 &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff},
3912 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
3913 fixWindowTitle(term);
3914 //XStoreName(xw.dpy, xw.win, opt_title);
3916 XSetForeground(xw.dpy, dc.gc, 0);
3917 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
3918 if (xw.tabheight > 0) XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
3920 XMapWindow(xw.dpy, xw.win);
3922 #if BLANKPTR_USE_GLYPH_CURSOR
3923 xw.blankPtr = XCreateGlyphCursor(xw.dpy, dc.font[0].fid, dc.font[0].fid, ' ', ' ', &blackcolor, &blackcolor);
3924 #else
3925 static const char cmbmp[1] = {0};
3926 Pixmap pm;
3928 pm = XCreateBitmapFromData(xw.dpy, xw.win, cmbmp, 1, 1);
3929 xw.blankPtr = XCreatePixmapCursor(xw.dpy, pm, pm, &blackcolor, &blackcolor, 0, 0);
3930 XFreePixmap(xw.dpy, pm);
3931 #endif
3933 XSync(xw.dpy, 0);
3937 static void xblankPointer (void) {
3938 if (!ptrBlanked && xw.blankPtr != None) {
3939 ptrBlanked = 1;
3940 XDefineCursor(xw.dpy, xw.win, xw.blankPtr);
3941 XFlush(xw.dpy);
3946 static void xunblankPointer (void) {
3947 if (ptrBlanked && xw.cursor != None) {
3948 ptrBlanked = 0;
3949 XDefineCursor(xw.dpy, xw.win, xw.cursor);
3950 XFlush(xw.dpy);
3951 ptrLastMove = mclock_ticks();
3956 static void xdraws (const char *s, const Glyph *base, int x, int y, int charlen, int bytelen) {
3957 int fg = base->fg, bg = base->bg, temp;
3958 int winx = x*xw.cw, winy = y*xw.ch+dc.font[0].ascent, width = charlen*xw.cw;
3959 XFontSet fontset = dc.font[0].set;
3960 int defF = base->attr&ATTR_DEFFG, defB = base->attr&ATTR_DEFBG;
3962 /* only switch default fg/bg if term is in RV mode */
3963 if (IS_SET(MODE_REVERSE)) {
3964 if (defF) fg = term->defbg;
3965 if (defB) bg = term->deffg;
3967 if (base->attr&ATTR_REVERSE) defF = defB = 0;
3968 if (base->attr&ATTR_BOLD) {
3969 if (defF && defB && defaultBoldFG >= 0) fg = defaultBoldFG;
3970 else if (fg < 8) fg += 8;
3971 fontset = dc.font[1].set;
3973 if ((base->attr&ATTR_UNDERLINE) && defaultUnderlineFG >= 0) {
3974 if (defF && defB) fg = defaultUnderlineFG;
3977 if (base->attr&ATTR_REVERSE) { temp = fg; fg = bg; bg = temp; }
3979 XSetBackground(xw.dpy, dc.gc, getColor(bg));
3980 XSetForeground(xw.dpy, dc.gc, getColor(fg));
3984 FILE *fo = fopen("zlog.log", "ab");
3985 fprintf(fo, "xdraws: x=%d; y=%d; charlen=%d; bytelen=%d\n", x, y, charlen, bytelen);
3986 fclose(fo);
3990 if (IS_GFX(base->attr)) {
3991 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
3992 } else if (!needConversion) {
3993 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
3994 } else {
3995 if (bytelen > 0) {
3996 //k8: dunno why, but Xutf8DrawImageString() ignores ascii (at least for terminus)
3997 const char *pos = s;
3998 int xpos = winx;
4000 while (pos < s+bytelen) {
4001 const char *e;
4002 int clen;
4004 if ((unsigned char)(pos[0]) < 128) {
4005 for (e = pos+1; e < s+bytelen && (unsigned char)(*e) < 128; ++e) ;
4006 clen = e-pos;
4007 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
4010 FILE *fo = fopen("zlog.log", "ab");
4011 fprintf(fo, " norm: clen=%d (%d); [", clen, (int)(e-pos));
4012 fwrite(pos, 1, e-pos, fo);
4013 fprintf(fo, "]\n");
4014 fclose(fo);
4017 } else {
4018 for (clen = 0, e = pos; e < s+bytelen && (unsigned char)(*e) >= 128; ++e) {
4019 if (((unsigned char)(e[0])&0xc0) == 0xc0) ++clen;
4021 Xutf8DrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
4024 FILE *fo = fopen("zlog.log", "ab");
4025 fprintf(fo, " utf8: clen=%d (%d); [", clen, (int)(e-pos));
4026 fwrite(pos, 1, e-pos, fo);
4027 fprintf(fo, "]\n");
4028 fclose(fo);
4032 xpos += xw.cw*clen;
4033 pos = e;
4038 if (opt_drawunderline && (base->attr&ATTR_UNDERLINE)) {
4039 XDrawLine(xw.dpy, term->picbuf, dc.gc, winx, winy+1, winx+width-1, winy+1);
4044 /* copy buffer pixmap to screen pixmap */
4045 static void xcopy (int x, int y, int cols, int rows) {
4046 int src_x = x*xw.cw, src_y = y*xw.ch, src_w = cols*xw.cw, src_h = rows*xw.ch;
4047 int dst_x = src_x, dst_y = src_y;
4049 if (opt_tabposition == 1) { dst_y += xw.tabheight; }
4050 XCopyArea(xw.dpy, term->picbuf, xw.win, dc.gc, src_x, src_y, src_w, src_h, dst_x, dst_y);
4054 static void xdrawcursor (void) {
4055 Glyph g;
4056 int sl, scrx, scry, cmy;
4058 if (term == NULL) return;
4060 LIMIT(term->oldcx, 0, term->col-1);
4061 LIMIT(term->oldcy, 0, term->row-1);
4063 cmy = term->row-term->topline-1;
4065 if (!(xw.state&WIN_FOCUSED) && !term->curblinkinactive) term->curbhidden = 0;
4067 if (term->cmdMode == CMDMODE_NONE || term->oldcy != cmy) {
4068 scrx = term->oldcx;
4069 scry = term->oldcy+term->topline;
4070 if (scry >= 0 && scry < term->row) {
4071 if (term->curbhidden < 0 ||
4072 term->oldcy != term->c.y || term->oldcx != term->c.x ||
4073 (term->c.state&CURSOR_HIDE) ||
4074 !(xw.state&WIN_FOCUSED)) {
4075 /* remove the old cursor */
4076 sl = utf8size(term->line[term->oldcy][scrx].c);
4077 g = term->line[term->oldcy][scrx];
4078 if (selected(scrx, term->c.y)) g.attr ^= ATTR_REVERSE;
4079 xdraws(g.c, &g, scrx, scry, 1, sl);
4080 //xclear(scrx, term->oldcy, scrx, term->oldcy);
4081 xcopy(scrx, scry, 1, 1);
4083 if (term->curbhidden) term->curbhidden = 1;
4087 if (term->cmdMode != CMDMODE_NONE && term->oldcy == cmy) return;
4088 if ((term->c.state&CURSOR_HIDE) != 0) return;
4089 if (term->curbhidden) return;
4090 /* draw the new one */
4091 scrx = term->c.x;
4092 scry = term->c.y+term->topline;
4093 if (scry >= 0 && scry < term->row) {
4094 int nodraw = 0;
4096 if (!(xw.state&WIN_FOCUSED)) {
4097 if (defaultCursorInactiveBG < 0) {
4098 XSetForeground(xw.dpy, dc.gc, getColor(defaultCursorBG));
4099 XDrawRectangle(xw.dpy, term->picbuf, dc.gc, scrx*xw.cw, scry*xw.ch, xw.cw-1, xw.ch-1);
4100 nodraw = 1;
4101 } else {
4102 g.bg = defaultCursorInactiveBG;
4103 g.fg = defaultCursorInactiveFG;
4105 } else {
4106 g.fg = defaultCursorFG;
4107 g.bg = defaultCursorBG;
4109 if (!nodraw) {
4110 memcpy(g.c, term->line[term->c.y][scrx].c, UTF_SIZ);
4111 g.state = 0;
4112 g.attr = ATTR_NULL;
4113 if (IS_SET(MODE_REVERSE)) g.attr |= ATTR_REVERSE;
4114 sl = utf8size(g.c);
4115 xdraws(g.c, &g, scrx, scry, 1, sl);
4117 term->oldcx = scrx;
4118 term->oldcy = term->c.y;
4119 xcopy(scrx, scry, 1, 1);
4124 static void xdrawTabBar (void) {
4125 if (xw.tabheight > 0 && updateTabBar) {
4126 static int tableft = 0;
4127 int tabstart;
4128 int tabw = xw.w/opt_tabcount;
4129 XFontSet fontset = dc.font[2].set;
4131 XSetForeground(xw.dpy, dc.gc, getColor(normalTabBG));
4132 XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
4134 if (termidx < tableft) tableft = termidx;
4135 else if (termidx > tableft+opt_tabcount-1) tableft = termidx-opt_tabcount+1;
4136 if (tableft < 0) tableft = 0;
4138 tabstart = tableft;
4139 for (int f = tabstart; f < tabstart+opt_tabcount; ++f) {
4140 int x = (f-tabstart)*tabw;
4141 const char *title;
4142 char *tit;
4144 if (f >= term_count) {
4145 XSetForeground(xw.dpy, dc.gc, getColor(normalTabBG));
4146 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, xw.w, xw.tabheight);
4148 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
4149 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
4150 break;
4152 title = term_array[f]->title;
4153 if (!title[0]) title = opt_title;
4154 tit = SPrintf("[%d]%s", f, title);
4155 title = tit;
4157 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
4158 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, tabw, xw.tabheight);
4160 XSetBackground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
4161 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabFG : normalTabFG));
4163 if (needConversion) {
4164 int xx = x+2;
4166 while (*title && xx < x+tabw) {
4167 const char *e = title;
4168 XRectangle r;
4170 memset(&r, 0, sizeof(r));
4172 if ((unsigned char)(*e) > 127) {
4173 while (*e && (unsigned char)(*e) > 127) ++e;
4174 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
4175 Xutf8TextExtents(fontset, title, e-title, &r, NULL);
4176 } else {
4177 while (*e && (unsigned char)(*e) <= 127) ++e;
4178 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
4179 XmbTextExtents(fontset, title, e-title, &r, NULL);
4181 title = e;
4182 xx += r.width-r.x;
4185 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
4186 } else {
4187 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
4189 free(tit);
4191 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
4192 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x+tabw-2, 0, 2, xw.tabheight);
4194 if (f > tabstart) {
4195 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
4196 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
4200 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
4201 //XDrawRectangle(xw.dpy, xw.pictab, dc.gc, -1, 0, xw.w+1, xw.tabheight+1);
4202 if (opt_tabposition == 0) {
4203 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, 0);
4204 } else {
4205 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, xw.tabheight-1, xw.w, xw.tabheight-1);
4208 if (opt_tabposition == 0) {
4209 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, xw.h-xw.tabheight);
4210 } else {
4211 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, 0);
4213 updateTabBar = 0;
4217 static void drawcmdline (int scry) {
4218 Glyph base;
4219 int cpos = term->cmdofs, bc = 0, x, sx;
4220 int back = (term->cmdMode == CMDMODE_INPUT ? 21 : 124);
4222 base.attr = ATTR_NULL;
4223 base.fg = 255;
4224 base.bg = back;
4225 base.state = 0;
4226 for (sx = x = 0; x < term->col && term->cmdline[cpos]; ++x) {
4227 int l = utf8size(term->cmdline+cpos);
4229 if (bc+l > DRAW_BUF_SIZ) {
4230 xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
4231 bc = 0;
4232 sx = x;
4234 memcpy(term->drawbuf+bc, term->cmdline+cpos, l);
4235 cpos += l;
4236 bc += l;
4238 if (bc > 0) xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
4240 if (x < term->col && term->cmdMode == CMDMODE_INPUT) {
4241 base.fg = back;
4242 base.bg = 255;
4243 xdraws(" ", &base, x, scry, 1, 1);
4244 ++x;
4247 if (x < term->col) {
4248 base.fg = 255;
4249 base.bg = back;
4250 memset(term->drawbuf, ' ', DRAW_BUF_SIZ);
4251 while (x < term->col) {
4252 sx = x;
4253 x += DRAW_BUF_SIZ;
4254 if (x > term->col) x = term->col;
4255 xdraws(term->drawbuf, &base, sx, scry, x-sx, x-sx);
4259 xcopy(0, scry, term->col, 1);
4263 static void drawline (int x1, int x2, int scry, int lineno) {
4264 //fprintf(stderr, "%d: drawline: x1=%d; x2=%d; scry=%d; row:%d; lineno=%d\n", mclock_ticks(), x1, x2, scry, term->row, lineno);
4265 if (scry < 0 || scry >= term->row) return;
4266 if (scry == term->row-1 && term->cmdMode != CMDMODE_NONE) { drawcmdline(scry); return; }
4267 if (lineno < 0 || lineno >= term->linecount) {
4268 xclear(0, scry, term->col-1, scry);
4269 xcopy(0, scry, term->col, 1);
4270 } else {
4271 int ic, ib, ox, sl;
4272 int stx, ex;
4273 Glyph base, new;
4275 if (lineno < term->row && term->topline == 0) {
4276 //if (term->topline != 0) term->dirty[lineno] = 2;
4277 if (!term->dirty[lineno]) return;
4278 // fix 'dirty' flag for line
4279 if (term->dirty[lineno]&0x02) {
4280 // mark full line as dirty
4281 for (int x = 0; x < term->col; ++x) term->line[lineno][x].state |= GLYPH_DIRTY;
4283 // correct 'dirty' flag
4284 term->dirty[lineno] = 0;
4285 if (x1 > 0) for (int x = 0; x < x1; ++x) if (term->line[lineno][x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
4286 if (!term->dirty[lineno] && x2 < term->col) for (int x = x2; x < term->col; ++x) if (term->line[lineno][x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
4288 // find dirty region
4289 for (stx = x1; stx < x2; ++stx) if (term->line[lineno][stx].state&GLYPH_DIRTY) break;
4290 for (ex = x2; ex > stx; --ex) if (term->line[lineno][ex-1].state&GLYPH_DIRTY) break;
4291 if (stx >= x2 || ex <= stx) return; // nothing to do
4292 } else {
4293 //if (lineno < term->row) term->dirty[lineno] = 0;
4294 stx = 0;
4295 ex = term->col;
4298 base = term->line[lineno][stx];
4299 ic = ib = 0;
4300 ox = stx;
4301 //xclear(stx, scry, ex-1, scry); //k8: actually, we don't need this for good monospace fonts, so...
4302 for (int x = stx; x < ex; ++x) {
4303 new = term->line[lineno][x];
4304 term->line[lineno][x].state &= ~GLYPH_DIRTY; //!
4305 if (term->sel.bx != -1 && new.c[0]) {
4306 if ((lineno < term->row && selected(x, lineno)) || (lineno >= term->row && selected(x, 0-(lineno-term->row+1)))) new.attr ^= ATTR_REVERSE;
4308 if (ib > 0 && (ATTRCMP(base, new) || ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
4309 // flush draw buffer
4310 xdraws(term->drawbuf, &base, ox, scry, ic, ib);
4311 ic = ib = 0;
4313 if (ib == 0) { ox = x; base = new; }
4314 sl = utf8size(new.c);
4315 memcpy(term->drawbuf+ib, new.c, sl);
4316 ib += sl;
4317 ++ic;
4319 if (ib > 0) xdraws(term->drawbuf, &base, ox, scry, ic, ib);
4320 //xcopy(0, scry, term->col, 1);
4321 //if (term->c.y == lineno && term->c.x >= stx && term->c.x < ex) xdrawcursor();
4322 xcopy(stx, scry, ex-stx, 1);
4327 static void drawregion (int x1, int y1, int x2, int y2, int forced) {
4328 if (!forced && (xw.state&WIN_VISIBLE) == 0) {
4329 //dlogf("invisible");
4330 lastDrawTime = term->lastDrawTime = 1;
4331 term->wantRedraw = 1;
4332 return;
4335 if (term->topline < term->row) {
4336 for (int y = y1; y < y2; ++y) drawline(x1, x2, y+term->topline, y);
4338 if (term->topline > 0) {
4339 int scry = MIN(term->topline, term->row), y = term->row;
4341 if (term->topline >= term->row) y += term->topline-term->row;
4342 while (--scry >= 0) {
4343 drawline(0, term->col, scry, y);
4344 ++y;
4347 xdrawcursor();
4348 xdrawTabBar();
4349 //XFlush(xw.dpy);
4350 lastDrawTime = term->lastDrawTime = mclock_ticks();
4351 term->wantRedraw = 0;
4355 static void draw (int forced) {
4356 if (term != NULL) {
4357 //fprintf(stderr, "draw(%d) (%d)\n", forced, mclock_ticks());
4358 drawregion(0, 0, term->col, term->row, forced);
4363 static void expose (XEvent *ev) {
4364 XExposeEvent *e = &ev->xexpose;
4366 if (xw.state&WIN_REDRAW) {
4367 if (!e->count && term != NULL) {
4368 xw.state &= ~WIN_REDRAW;
4369 xcopy(0, 0, term->col, term->row);
4371 } else if (term != NULL) {
4372 //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)));
4373 xcopy(0, 0, term->col, term->row);
4375 xdrawTabBar();
4376 //XFlush(xw.dpy);
4380 static void visibility (XEvent *ev) {
4381 XVisibilityEvent *e = &ev->xvisibility;
4383 if (e->state == VisibilityFullyObscured) xw.state &= ~WIN_VISIBLE;
4384 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 */
4388 static void unmap (XEvent *ev) {
4389 xw.state &= ~WIN_VISIBLE;
4393 static void xseturgency (int add) {
4394 XWMHints *h = XGetWMHints(xw.dpy, xw.win);
4396 h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
4397 XSetWMHints(xw.dpy, xw.win, h);
4398 XFree(h);
4402 static void focus (XEvent *ev) {
4403 if (ev->type == FocusIn) {
4404 xw.state |= WIN_FOCUSED;
4405 xseturgency(0);
4406 tsendfocusevent(1);
4407 } else {
4408 xw.state &= ~WIN_FOCUSED;
4409 tsendfocusevent(0);
4411 //draw(1);
4412 xdrawcursor();
4413 xdrawTabBar();
4414 xcopy(0, 0, term->col, term->row);
4418 ////////////////////////////////////////////////////////////////////////////////
4419 // keyboard mapping
4420 static const char *kmap (KeySym k, uint state) {
4421 const char *res = NULL;
4423 state &= ~Mod2Mask; // numlock
4424 for (int f = 0; f < keymap_used; ++f) {
4425 uint mask = keymap[f].mask;
4427 if (keymap[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) {
4428 //fprintf(stderr, "kp: %d\n", IS_SET(MODE_APPKEYPAD));
4429 if (!IS_SET(MODE_APPKEYPAD)) {
4430 if (!keymap[f].kp) {
4431 //fprintf(stderr, "nokp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4432 return keymap[f].str; // non-keypad hit
4434 continue;
4436 //fprintf(stderr, "kp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4437 if (keymap[f].kp) return keymap[f].str; // keypad hit
4438 res = keymap[f].str; // kp mode, but non-kp mapping found
4441 return res;
4445 static const char *kbind (KeySym k, uint state) {
4446 state &= ~Mod2Mask; // numlock
4447 for (int f = 0; f < keybinds_used; ++f) {
4448 uint mask = keybinds[f].mask;
4450 if (keybinds[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) return keybinds[f].str;
4452 return NULL;
4456 static KeySym do_keytrans (KeySym ks) {
4457 for (int f = 0; f < keytrans_used; ++f) if (keytrans[f].src == ks) return keytrans[f].dst;
4458 return ks;
4462 static void cmdline_closequeryexec (int cancelled) {
4463 if (!cancelled) {
4464 char *rep = term->cmdline+term->cmdreslen;
4466 trimstr(rep);
4467 if (!rep[0] || strchr("yt", tolower(rep[0])) != NULL) {
4468 closeRequestComes = 2;
4469 return;
4472 closeRequestComes = 0;
4476 static void kpress (XEvent *ev) {
4477 XKeyEvent *e = &ev->xkey;
4478 KeySym ksym = NoSymbol;
4479 const char *kstr;
4480 int len;
4481 Status status;
4482 char buf[32];
4484 if (term == NULL) return;
4486 if (!ptrBlanked && opt_ptrblank > 0) xblankPointer();
4488 //if (len < 1) len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
4489 if ((len = Xutf8LookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status)) > 0) buf[len] = 0;
4490 // leave only known mods
4491 e->state &= (Mod1Mask | Mod4Mask | ControlMask | ShiftMask);
4492 #ifdef DUMP_KEYSYMS
4494 const char *ksname = XKeysymToString(ksym);
4496 fprintf(stderr, "utf(%d):[%s] (%s) 0x%08x\n", len, len>=0?buf:"<shit>", ksname, (unsigned int)e->state);
4498 #endif
4499 if ((kstr = kbind(ksym, e->state)) != NULL) {
4500 // keybind found
4501 executeCommands(kstr);
4502 return;
4505 if (term->cmdMode != CMDMODE_NONE) {
4506 int mode = term->cmdMode;
4508 switch (do_keytrans(ksym)) {
4509 case XK_Return:
4510 tcmdlinehide();
4511 if (term->cmdexecfn) term->cmdexecfn(0);
4512 else if (mode == CMDMODE_INPUT) executeCommands(term->cmdline);
4513 break;
4514 case XK_BackSpace:
4515 if (mode == CMDMODE_INPUT) {
4516 tcmdlinechoplast();
4517 term->cmdtabpos = -1;
4518 term->cmdprevc = NULL;
4519 } else {
4520 tcmdlinehide();
4521 if (term->cmdexecfn) term->cmdexecfn(1);
4523 break;
4524 case XK_Escape:
4525 tcmdlinehide();
4526 if (term->cmdexecfn) term->cmdexecfn(1);
4527 break;
4528 case XK_Tab:
4529 if (mode == CMDMODE_INPUT && term->cmdline[0] && term->cmdcl == 0 && term->cmdexecfn == NULL) {
4530 const char *cpl;
4532 if (term->cmdtabpos < 0) {
4533 term->cmdtabpos = 0;
4534 while (term->cmdline[term->cmdtabpos] && isalnum(term->cmdline[term->cmdtabpos])) ++term->cmdtabpos;
4535 if (term->cmdline[term->cmdtabpos]) {
4536 term->cmdtabpos = -1;
4537 break;
4539 term->cmdprevc = NULL;
4541 cpl = findCommandCompletion(term->cmdline, term->cmdtabpos, term->cmdprevc);
4542 if (cpl == NULL && term->cmdprevc != NULL) cpl = findCommandCompletion(term->cmdline, term->cmdtabpos, NULL);
4543 term->cmdprevc = cpl;
4544 if (cpl != NULL) strcpy(term->cmdline, cpl);
4545 tcmdlinefixofs();
4546 } else if (mode != CMDMODE_INPUT) {
4547 tcmdlinehide();
4548 if (term->cmdexecfn) term->cmdexecfn(1);
4550 break;
4551 default:
4552 if (mode == CMDMODE_INPUT) {
4553 if (len > 0 && (unsigned char)buf[0] >= 32) {
4554 tcmdput(buf, len);
4555 term->cmdtabpos = -1;
4556 term->cmdprevc = NULL;
4559 break;
4561 return;
4564 if ((kstr = kmap(do_keytrans(ksym), e->state)) != NULL) {
4565 if (kstr[0]) {
4566 tunshowhistory();
4567 ttywritestr(kstr);
4569 } else {
4570 int meta = (e->state&Mod1Mask);
4572 int shift = (e->state&ShiftMask);
4573 int ctrl = (e->state&ControlMask);
4575 switch (ksym) {
4576 case XK_Return:
4577 tunshowhistory();
4578 if (meta) {
4579 ttywritestr("\x1b\x0a");
4580 } else {
4581 if (IS_SET(MODE_CRLF)) ttywritestr("\r\n"); else ttywritestr("\r");
4583 break;
4584 default:
4585 if (len > 0) {
4586 tunshowhistory();
4587 if (meta && len == 1) ttywritestr("\x1b");
4588 ttywrite(buf, len);
4590 break;
4596 ////////////////////////////////////////////////////////////////////////////////
4597 // xembed?
4598 static void cmessage (XEvent *e) {
4599 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
4600 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
4601 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
4602 xw.state |= WIN_FOCUSED;
4603 xseturgency(0);
4604 tsendfocusevent(1);
4605 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
4606 xw.state &= ~WIN_FOCUSED;
4607 tsendfocusevent(0);
4609 xdrawcursor();
4610 xdrawTabBar();
4611 xcopy(0, 0, term->col, term->row);
4612 return;
4615 if (e->xclient.data.l[0] == XA_WM_DELETE_WINDOW) {
4616 closeRequestComes = 1;
4617 return;
4622 ////////////////////////////////////////////////////////////////////////////////
4623 static void resize (XEvent *e) {
4624 int col, row;
4625 Term *ot = term;
4627 //if (e->xconfigure.width == 65535 || e->xconfigure.width == -1) e->xconfigure.width = xw.w;
4628 if (e->xconfigure.height == 65535 || e->xconfigure.height == -1) e->xconfigure.height = xw.h;
4629 //if ((short int)e->xconfigure.height < xw.ch) return;
4631 if (e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) return;
4632 xw.w = e->xconfigure.width;
4633 xw.h = e->xconfigure.height;
4634 col = xw.w/xw.cw;
4635 row = (xw.h-xw.tabheight)/xw.ch;
4636 //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);
4637 if (col == term->col && row == term->row) return;
4638 for (int f = 0; f < term_count; ++f) {
4639 term = term_array[f];
4640 if (tresize(col, row) && ot == term) draw(1);
4641 ttyresize();
4642 xresize(col, row);
4644 term = ot;
4645 XFreePixmap(xw.dpy, xw.pictab);
4646 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
4647 updateTabBar = 1;
4651 static inline int last_draw_too_old (void) {
4652 int tt = mclock_ticks();
4654 if (term != NULL) {
4655 if (term->dead || !term->wantRedraw) return 0;
4656 if (tt-term->lastDrawTime >= opt_drawtimeout) return 1;
4657 return 0;
4659 return (tt-lastDrawTime >= 500);
4663 ////////////////////////////////////////////////////////////////////////////////
4664 // main loop
4665 static void (*handler[LASTEvent])(XEvent *) = {
4666 [KeyPress] = kpress,
4667 [ClientMessage] = cmessage,
4668 [ConfigureNotify] = resize,
4669 [VisibilityNotify] = visibility,
4670 [UnmapNotify] = unmap,
4671 [Expose] = expose,
4672 [FocusIn] = focus,
4673 [FocusOut] = focus,
4674 [MotionNotify] = bmotion,
4675 [ButtonPress] = bpress,
4676 [ButtonRelease] = brelease,
4677 [SelectionNotify] = selnotify,
4678 [SelectionRequest] = selrequest,
4679 [SelectionClear] = selclear,
4683 static void run (void) {
4684 //int stuff_to_print = 0;
4685 int xfd = XConnectionNumber(xw.dpy);
4687 ptrLastMove = mclock_ticks();
4688 while (term_count > 0) {
4689 XEvent ev;
4690 fd_set rfd, wfd;
4691 struct timeval timeout;
4692 int maxfd = xfd;
4693 Term *ot;
4694 int dodraw;
4696 FD_ZERO(&rfd);
4697 FD_ZERO(&wfd);
4698 FD_SET(xfd, &rfd);
4699 //FD_SET(term->cmdfd, &rfd);
4700 // have something to write?
4701 for (int f = 0; f < term_count; ++f) {
4702 Term *t = term_array[f];
4704 if (!t->dead && term->cmdfd >= 0 && t->pid != 0) {
4705 if (t->cmdfd > maxfd) maxfd = t->cmdfd;
4706 FD_SET(t->cmdfd, &rfd);
4707 if (t->wrbufpos < t->wrbufused) FD_SET(t->cmdfd, &wfd);
4711 timeout.tv_sec = 0;
4712 timeout.tv_usec = (opt_drawtimeout+2)*1000;
4713 if (select(maxfd+1, &rfd, &wfd, NULL, &timeout) < 0) {
4714 if (errno == EINTR) continue;
4715 die("select failed: %s", SERRNO);
4718 ot = term;
4719 for (int f = 0; f < term_count; ++f) {
4720 Term *t = term_array[f];
4722 if (!t->dead && term->cmdfd >= 0 && term->pid != 0) {
4723 term = t;
4724 if (FD_ISSET(t->cmdfd, &wfd)) ttyflushwrbuf();
4725 if (FD_ISSET(t->cmdfd, &rfd)) ttyread(); //t->wantRedraw = 1;
4726 term = ot;
4730 termcleanup();
4731 if (term_count == 0) exit(exitcode);
4733 dodraw = 0;
4734 if (term != NULL && term->curblink > 0) {
4735 int tt = mclock_ticks();
4737 if (tt-term->lastBlinkTime >= term->curblink) {
4738 term->lastBlinkTime = tt;
4739 if ((xw.state&WIN_FOCUSED) || term->curblinkinactive) {
4740 term->curbhidden = (term->curbhidden ? 0 : -1);
4741 dodraw = 1;
4742 } else {
4743 term->curbhidden = 0;
4747 if (dodraw || updateTabBar || last_draw_too_old()) draw(0);
4749 if (XPending(xw.dpy)) {
4750 while (XPending(xw.dpy)) {
4751 XNextEvent(xw.dpy, &ev);
4752 switch (ev.type) {
4753 //case VisibilityNotify:
4754 //case UnmapNotify:
4755 //case FocusIn:
4756 //case FocusOut:
4757 case MotionNotify:
4758 case ButtonPress:
4759 case ButtonRelease:
4760 xunblankPointer();
4761 ptrLastMove = mclock_ticks();
4762 break;
4763 default: ;
4765 if (XFilterEvent(&ev, xw.win)) continue;
4766 if (handler[ev.type]) (handler[ev.type])(&ev);
4770 if (term != NULL) {
4771 switch (closeRequestComes) {
4772 case 1: // just comes
4773 if (opt_ignoreclose == 0) {
4774 tcmdlinehide();
4775 tcmdlineinitex("Do you really want to close sterm [Y/n]? ");
4776 term->cmdexecfn = cmdline_closequeryexec;
4777 } else if (opt_ignoreclose < 0) {
4778 //FIXME: kill all clients?
4779 return;
4781 closeRequestComes = 0;
4782 break;
4783 case 2: // ok, die now
4784 //FIXME: kill all clients?
4785 return;
4789 if (!ptrBlanked && opt_ptrblank > 0 && mclock_ticks()-ptrLastMove >= opt_ptrblank) {
4790 xblankPointer();
4796 ////////////////////////////////////////////////////////////////////////////////
4797 typedef const char * (*IniHandlerFn) (const char *optname, const char *fmt, char *argstr, void *udata);
4800 typedef struct {
4801 const char *name;
4802 const char *fmt;
4803 void *udata;
4804 IniHandlerFn fn;
4805 } IniCommand;
4807 static const char *inifnGenericOneArg (const char *optname, const char *fmt, char *argstr, void *udata) {
4808 return iniParseArguments(argstr, fmt, udata);
4812 static const char *inifnGenericOneStr (const char *optname, const char *fmt, char *argstr, void *udata) {
4813 char *s = NULL;
4814 const char *err = iniParseArguments(argstr, fmt, &s);
4816 if (err != NULL) return err;
4817 if ((s = strdup(s)) == NULL) return "out of memory";
4818 if (udata) {
4819 char **ustr = (char **)udata;
4821 if (*ustr) free(*ustr);
4822 *ustr = s;
4824 return NULL;
4828 static const IniCommand iniCommands[] = {
4829 {"term", "s!-", &opt_term, inifnGenericOneStr},
4830 {"class", "s!-", &opt_class, inifnGenericOneStr},
4831 {"title", "s!-", &opt_title, inifnGenericOneStr},
4832 {"fontnorm", "s!-", &opt_fontnorm, inifnGenericOneStr},
4833 {"fontbold", "s!-", &opt_fontbold, inifnGenericOneStr},
4834 {"fonttab", "s!-", &opt_fonttab, inifnGenericOneStr},
4835 {"shell", "s!-", &opt_shell, inifnGenericOneStr},
4836 {"doubleclick_timeout", "i{0,10000}", &opt_doubleclick_timeout, inifnGenericOneArg},
4837 {"tripleclick_timeout", "i{0,10000}", &opt_tripleclick_timeout, inifnGenericOneArg},
4838 {"tabsize", "i{1,256}", &opt_tabsize, inifnGenericOneArg},
4839 {"defaultfg", "i{0,511}", &defaultFG, inifnGenericOneArg},
4840 {"defaultbg", "i{0,511}", &defaultBG, inifnGenericOneArg},
4841 {"defaultcursorfg", "i{0,511}", &defaultCursorFG, inifnGenericOneArg},
4842 {"defaultcursorbg", "i{0,511}", &defaultCursorBG, inifnGenericOneArg},
4843 {"defaultinactivecursorfg", "i{0,511}", &defaultCursorInactiveFG, inifnGenericOneArg},
4844 {"defaultinactivecursorbg", "i{-1,511}", &defaultCursorInactiveBG, inifnGenericOneArg},
4845 {"defaultboldfg", "i{-1,511}", &defaultBoldFG, inifnGenericOneArg},
4846 {"defaultunderlinefg", "i{-1,511}", &defaultUnderlineFG, inifnGenericOneArg},
4847 {"normaltabfg", "i{0,511}", &normalTabFG, inifnGenericOneArg},
4848 {"normaltabbg", "i{0,511}", &normalTabBG, inifnGenericOneArg},
4849 {"activetabfg", "i{0,511}", &activeTabFG, inifnGenericOneArg},
4850 {"activetabbg", "i{0,511}", &activeTabBG, inifnGenericOneArg},
4851 {"maxhistory", "i{0,65535}", &opt_maxhistory, inifnGenericOneArg},
4852 {"ptrblank", "i{0,65535}", &opt_ptrblank, inifnGenericOneArg},
4853 {"tabcount", "i{1,128}", &opt_tabcount, inifnGenericOneArg},
4854 {"tabposition", "i{0,1}", &opt_tabposition, inifnGenericOneArg},
4855 {"draw_timeout", "i{5,30000}", &opt_drawtimeout, inifnGenericOneArg},
4856 {"audiblebell", "b", &opt_audiblebell, inifnGenericOneArg},
4857 {"urgentbell", "b", &opt_urgentbell, inifnGenericOneArg},
4858 {"cursorblink", "i{0,10000}", &opt_cursorBlink, inifnGenericOneArg},
4859 {"cursorblinkinactive", "b", &opt_cursorBlinkInactive, inifnGenericOneArg},
4860 {"drawunderline", "b", &opt_drawunderline, inifnGenericOneArg},
4861 {"ignoreclose", "i{-1,1}", &opt_ignoreclose, inifnGenericOneArg},
4862 {NULL, NULL, NULL, NULL}
4866 #define MISC_CMD_NONE ((const char *)-1)
4869 // NULL: command processed; MISC_CMD_NONE: unknown command; !NULL: error
4870 static const char *processMiscCmds (const char *optname, char *argstr) {
4871 const char *err = NULL;
4873 if (strcasecmp(optname, "unimap") == 0) {
4874 int uni, ch;
4875 char *alt = NULL;
4877 //unimap 0x2592 0x61 alt
4878 if ((err = iniParseArguments(argstr, "i{0,65535}i{0,255}|s!-", &uni, &ch, &alt)) != NULL) return err;
4879 if (alt != NULL && strcasecmp(alt, "alt") != 0) return "invalid unimap";
4880 if (unimap == NULL) {
4881 if ((unimap = calloc(65536, sizeof(unimap[0]))) == NULL) return "out of memory";
4883 if (alt != NULL && ch == 0) alt = NULL;
4884 if (alt != NULL && ch < 96) return "invalid unimap";
4885 unimap[uni] = ch;
4886 if (alt != NULL) unimap[uni] |= 0x8000;
4887 return NULL;
4890 if (strcasecmp(optname, "keytrans_reset") == 0) {
4891 if ((err = iniParseArguments(argstr, "")) != NULL) return err;
4892 keytrans_reset();
4893 return NULL;
4895 if (strcasecmp(optname, "keytrans") == 0) {
4896 char *src = NULL, *dst = NULL;
4898 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
4899 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
4900 keytrans_reset();
4901 return NULL;
4904 if ((err = iniParseArguments(argstr, "s!-s!-", &src, &dst)) != NULL) return err;
4905 keytrans_add(src, dst);
4906 return NULL;
4909 if (strcasecmp(optname, "keybind_reset") == 0) {
4910 if ((err = iniParseArguments(argstr, "")) != NULL) return err;
4911 keybinds_reset();
4912 return NULL;
4914 if (strcasecmp(optname, "keybind") == 0) {
4915 char *key = NULL, *act = NULL;
4916 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
4917 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
4918 keybinds_reset();
4919 return NULL;
4922 if ((err = iniParseArguments(argstr, "s!-R!", &key, &act)) != NULL) return err;
4923 keybind_add(key, act);
4924 return NULL;
4927 if (strcasecmp(optname, "keymap_reset") == 0) {
4928 if ((err = iniParseArguments(argstr, "")) != NULL) return err;
4929 keymap_reset();
4930 return NULL;
4932 if (strcasecmp(optname, "keymap") == 0) {
4933 char *key = NULL, *str = NULL;
4935 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
4936 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
4937 keymap_reset();
4938 return NULL;
4941 if ((err = iniParseArguments(argstr, "s!-s!-", &key, &str)) != NULL) return err;
4942 keymap_add(key, str);
4943 return NULL;
4946 return MISC_CMD_NONE;
4950 #define INI_LINE_SIZE (32768)
4952 // <0: file not found
4953 // >0: file loading error
4954 // 0: ok
4955 static int loadConfig (const char *fname) {
4956 int inifelse = 0; // 0: not; 1: doing true; 2: doing false; -1: waiting else/endif; -2: waiting endif
4957 FILE *fi = fopen(fname, "r");
4958 const char *err = NULL;
4959 char *line;
4960 int lineno = 0;
4962 if (fi == NULL) return -1;
4963 if ((line = malloc(INI_LINE_SIZE)) == NULL) { err = "out of memory"; goto quit; }
4965 while (fgets(line, INI_LINE_SIZE-1, fi) != NULL) {
4966 char *optname, *argstr;
4967 int goodoption = 0;
4969 ++lineno;
4970 line[INI_LINE_SIZE-1] = 0;
4971 // get option name
4972 for (optname = line; *optname && isspace(*optname); ++optname) ;
4973 if (!optname[0] || optname[0] == '#') continue; // comment
4974 if (!isalnum(optname[0])) { err = "invalid option name"; goto quit; }
4975 argstr = optname;
4976 while (*argstr) {
4977 if (!argstr[0] || isspace(argstr[0])) break;
4978 if (!isalnum(argstr[0]) && argstr[0] != '_' && argstr[0] != '.') { err = "invalid option name"; goto quit; }
4979 *argstr = tolower(*argstr);
4980 ++argstr;
4982 if (*argstr) *argstr++ = 0;
4984 if (strcasecmp(optname, "ifterm") == 0) {
4985 char *val;
4987 if (inifelse != 0) { err = "nested ifs are not allowed"; goto quit; }
4988 inifelse = -1;
4989 if ((err = iniParseArguments(argstr, "s", &val)) != NULL) goto quit;
4990 if (strcasecmp(opt_term, val) == 0) inifelse = 1;
4991 continue;
4993 if (strcasecmp(optname, "else") == 0) {
4994 switch (inifelse) {
4995 case -1: inifelse = 2; break;
4996 case 2: case -2: case 0: err = "else without if"; goto quit;
4997 case 1: inifelse = -2; break;
4999 continue;
5001 if (strcasecmp(optname, "endif") == 0) {
5002 switch (inifelse) {
5003 case -1: case -2: case 1: case 2: inifelse = 0; break;
5004 case 0: err = "endif without if"; goto quit;
5006 continue;
5009 if (inifelse < 0) {
5010 //trimstr(argstr);
5011 //fprintf(stderr, "skip: [%s]\n", argstr);
5012 continue;
5014 if (opt_term_locked && strcasecmp(optname, "term") == 0) continue; // termname given in command line
5015 // ok, we have option name in `optname` and arguments in `argstr`
5016 if (strncmp(optname, "color.", 6) == 0) {
5017 int n = 0;
5018 char *s = NULL;
5020 optname += 6;
5021 if (!optname[0]) { err = "invalid color option"; goto quit; }
5022 while (*optname) {
5023 if (!isdigit(*optname)) { err = "invalid color option"; goto quit; }
5024 n = (n*10)+(optname[0]-'0');
5025 ++optname;
5027 if (n < 0 || n > 511) { err = "invalid color index"; goto quit; }
5029 if ((err = iniParseArguments(argstr, "s!-", &s)) != NULL) goto quit;
5030 if ((s = strdup(s)) == NULL) { err = "out of memory"; goto quit; }
5031 if (opt_colornames[n] != NULL) free(opt_colornames[n]);
5032 opt_colornames[n] = s;
5033 continue;
5036 if ((err = processMiscCmds(optname, argstr)) != MISC_CMD_NONE) {
5037 if (err != NULL) goto quit;
5038 continue;
5039 } else {
5040 err = NULL;
5043 for (int f = 0; iniCommands[f].name != NULL; ++f) {
5044 if (strcmp(iniCommands[f].name, optname) == 0) {
5045 if ((err = iniCommands[f].fn(optname, iniCommands[f].fmt, argstr, iniCommands[f].udata)) != NULL) goto quit;
5046 goodoption = 1;
5047 break;
5050 if (!goodoption) {
5051 fprintf(stderr, "ini error at line %d: unknown option '%s'!\n", lineno, optname);
5054 quit:
5055 if (line != NULL) free(line);
5056 fclose(fi);
5057 if (err == NULL && inifelse != 0) err = "if without endif";
5058 if (err != NULL) die("ini error at line %d: %s", lineno, err);
5059 return 0;
5063 static void initDefaultOptions (void) {
5064 opt_title = strdup("sterm");
5065 opt_class = strdup("sterm");
5066 opt_term = strdup(TNAME);
5067 opt_fontnorm = strdup(FONT);
5068 opt_fontbold = strdup(FONTBOLD);
5069 opt_fonttab = strdup(FONTTAB);
5070 opt_shell = strdup(SHELL);
5072 memset(opt_colornames, 0, sizeof(opt_colornames));
5073 for (int f = 0; f < LEN(defcolornames); ++f) opt_colornames[f] = strdup(defcolornames[f]);
5074 for (int f = 0; f < LEN(defextcolornames); ++f) opt_colornames[f+256] = strdup(defextcolornames[f]);
5076 keytrans_add("KP_Home", "Home");
5077 keytrans_add("KP_Left", "Left");
5078 keytrans_add("KP_Up", "Up");
5079 keytrans_add("KP_Right", "Right");
5080 keytrans_add("KP_Down", "Down");
5081 keytrans_add("KP_Prior", "Prior");
5082 keytrans_add("KP_Next", "Next");
5083 keytrans_add("KP_End", "End");
5084 keytrans_add("KP_Begin", "Begin");
5085 keytrans_add("KP_Insert", "Insert");
5086 keytrans_add("KP_Delete", "Delete");
5088 keybind_add("shift+Insert", "PastePrimary");
5089 keybind_add("alt+Insert", "PasteCliboard");
5090 keybind_add("ctrl+alt+t", "NewTab");
5091 keybind_add("ctrl+alt+Left", "SwitchToTab prev");
5092 keybind_add("ctrl+alt+Right", "SwitchToTab next");
5094 keymap_add("BackSpace", "\177");
5095 keymap_add("Insert", "\x1b[2~");
5096 keymap_add("Delete", "\x1b[3~");
5097 keymap_add("Home", "\x1b[1~");
5098 keymap_add("End", "\x1b[4~");
5099 keymap_add("Prior", "\x1b[5~");
5100 keymap_add("Next", "\x1b[6~");
5101 keymap_add("F1", "\x1bOP");
5102 keymap_add("F2", "\x1bOQ");
5103 keymap_add("F3", "\x1bOR");
5104 keymap_add("F4", "\x1bOS");
5105 keymap_add("F5", "\x1b[15~");
5106 keymap_add("F6", "\x1b[17~");
5107 keymap_add("F7", "\x1b[18~");
5108 keymap_add("F8", "\x1b[19~");
5109 keymap_add("F9", "\x1b[20~");
5110 keymap_add("F10", "\x1b[21~");
5111 keymap_add("Up", "\x1bOA");
5112 keymap_add("Down", "\x1bOB");
5113 keymap_add("Right", "\x1bOC");
5114 keymap_add("Left", "\x1bOD");
5118 ////////////////////////////////////////////////////////////////////////////////
5119 static Term *oldTerm;
5120 static int oldTermIdx;
5121 static Term *newTerm;
5122 static int newTermIdx;
5123 static int newTermSwitch;
5126 typedef void (*CmdHandlerFn) (const char *cmdname, char *argstr);
5128 typedef struct {
5129 const char *name;
5130 CmdHandlerFn fn;
5131 } Command;
5134 static void cmdPastePrimary (const char *cmdname, char *argstr) {
5135 selpaste(XA_PRIMARY);
5139 static void cmdPasteSecondary (const char *cmdname, char *argstr) {
5140 selpaste(XA_SECONDARY);
5144 static void cmdPasteClipboard (const char *cmdname, char *argstr) {
5145 selpaste(XA_CLIPBOARD);
5149 static void cmdExec (const char *cmdname, char *argstr) {
5150 if (term != NULL) {
5151 if (term->execcmd != NULL) free(term->execcmd);
5152 term->execcmd = (argstr[0] ? strdup(argstr) : NULL);
5157 static int parseTabArgs (char *argstr, int *noswitch, int nowrap, int idx) {
5158 for (;;) {
5159 char *arg;
5161 while (*argstr && isspace(*argstr)) ++argstr;
5162 if (!argstr[0]) break;
5163 if (iniParseArguments(argstr, "s-R-", &arg, &argstr) != NULL) break;
5165 if (strcasecmp(arg, "noswitch") == 0) *noswitch = 1;
5166 else if (strcasecmp(arg, "switch") == 0) *noswitch = 0;
5167 else if (strcasecmp(arg, "nowrap") == 0) nowrap = 1;
5168 else if (strcasecmp(arg, "wrap") == 0) nowrap = 0;
5169 else if (strcasecmp(arg, "first") == 0) idx = 0;
5170 else if (strcasecmp(arg, "last") == 0) idx = term_count-1;
5171 else if (strcasecmp(arg, "prev") == 0) idx = -1;
5172 else if (strcasecmp(arg, "next") == 0) idx = -2;
5173 else {
5174 long int n = -1;
5175 char *eptr;
5177 n = strtol(arg, &eptr, 0);
5178 if (!eptr[0] && n >= 0 && n < term_count) idx = n;
5181 switch (idx) {
5182 case -1: // prev
5183 if ((idx = termidx-1) < 0) idx = nowrap ? 0 : term_count-1;
5184 break;
5185 case -2: // next
5186 if ((idx = termidx+1) >= term_count) idx = nowrap ? term_count-1 : 0;
5187 break;
5189 return idx;
5193 static void flushNewTerm (void) {
5194 if (newTerm != NULL) {
5195 if (newTermSwitch && term != NULL) term->lastActiveTime = mclock_ticks();
5196 term = newTerm;
5197 termidx = newTermIdx;
5198 tinitialize(term_array[0]->col, term_array[0]->row);
5200 term->picbufh = term->row*xw.ch;
5201 term->picbufw = term->col*xw.cw;
5202 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
5203 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
5205 if (ttynew(term) != 0) {
5206 term = oldTerm;
5207 termidx = oldTermIdx;
5208 termfree(newTermIdx);
5209 } else {
5210 selinit();
5211 ttyresize();
5212 if (newTermSwitch) {
5213 term = NULL;
5214 termidx = 0;
5215 switchToTerm(newTermIdx, 1);
5216 oldTerm = term;
5217 oldTermIdx = termidx;
5218 } else {
5219 term = oldTerm;
5220 termidx = oldTermIdx;
5223 newTerm = NULL;
5228 static void cmdNewTab (const char *cmdname, char *argstr) {
5229 int noswitch = 0, idx;
5231 if (opt_disabletabs) return;
5232 flushNewTerm();
5233 if ((newTerm = termalloc()) == NULL) return;
5234 /*idx =*/ parseTabArgs(argstr, &noswitch, 0, termidx);
5235 idx = term_count-1;
5236 if (!noswitch) {
5237 if (term != NULL) term->lastActiveTime = mclock_ticks();
5238 oldTermIdx = idx;
5240 newTermIdx = termidx = idx;
5241 term = newTerm;
5242 newTermSwitch = !noswitch;
5246 static void cmdCloseTab (const char *cmdname, char *argstr) {
5247 flushNewTerm();
5248 if (term != NULL && !term->dead) kill(term->pid, SIGTERM);
5252 static void cmdKillTab (const char *cmdname, char *argstr) {
5253 flushNewTerm();
5254 if (!term->dead) kill(term->pid, SIGKILL);
5258 static void cmdSwitchToTab (const char *cmdname, char *argstr) {
5259 int noswitch = 0, idx;
5261 flushNewTerm();
5262 idx = parseTabArgs(argstr, &noswitch, 0, -666);
5263 if (idx >= 0) {
5264 switchToTerm(idx, 1);
5265 oldTerm = term;
5266 oldTermIdx = termidx;
5271 static void cmdMoveTabTo (const char *cmdname, char *argstr) {
5272 int noswitch = 0, idx;
5274 flushNewTerm();
5275 idx = parseTabArgs(argstr, &noswitch, 0, termidx);
5276 if (idx != termidx && idx >= 0 && idx < term_count) {
5277 Term *t = term_array[termidx];
5279 // remove current term
5280 for (int f = termidx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
5281 // insert term
5282 for (int f = term_count-2; f >= idx; --f) term_array[f+1] = term_array[f];
5283 term_array[idx] = t;
5284 termidx = idx;
5285 oldTerm = t;
5286 oldTermIdx = idx;
5287 updateTabBar = 1;
5292 static void cmdDefaultFG (const char *cmdname, char *argstr) {
5293 char *s = NULL;
5294 int c;
5296 if (iniParseArguments(argstr, "i{0,511}|s-", &c, &s) == NULL) {
5297 if (s != NULL && tolower(s[0]) == 'g') defaultFG = c; else term->deffg = c;
5302 static void cmdDefaultBG (const char *cmdname, char *argstr) {
5303 char *s = NULL;
5304 int c;
5306 if (iniParseArguments(argstr, "i{0,511}|s-", &c) == NULL) {
5307 if (s != NULL && tolower(s[0]) == 'g') defaultBG = c; else term->defbg = c;
5312 static void scrollHistory (int delta) {
5313 if (term->maxhistory < 1) return; // no history
5314 term->topline += delta;
5315 if (term->topline > term->maxhistory) term->topline = term->maxhistory;
5316 if (term->topline < 0) term->topline = 0;
5317 tfulldirt();
5318 draw(1);
5322 static void cmdScrollHistoryLineUp (const char *cmdname, char *argstr) {
5323 scrollHistory(1);
5327 static void cmdScrollHistoryPageUp (const char *cmdname, char *argstr) {
5328 scrollHistory(term->row);
5332 static void cmdScrollHistoryLineDown (const char *cmdname, char *argstr) {
5333 scrollHistory(-1);
5337 static void cmdScrollHistoryPageDown (const char *cmdname, char *argstr) {
5338 scrollHistory(-term->row);
5342 static void cmdScrollHistoryTop (const char *cmdname, char *argstr) {
5343 scrollHistory(term->linecount);
5347 static void cmdScrollHistoryBottom (const char *cmdname, char *argstr) {
5348 scrollHistory(-term->linecount);
5352 static void cmdUTF8Locale (const char *cmdname, char *argstr) {
5353 int b;
5355 if (iniParseArguments(argstr, "b", &b) == NULL) {
5356 if (!needConversion) b = 1;
5357 if (term != NULL) term->needConv = !b;
5358 //fprintf(stderr, "needConv: %d (%d)\n", term->needConv, needConversion);
5363 static void cmdCommandMode (const char *cmdname, char *argstr) {
5364 if (term != NULL) {
5365 if (term->cmdMode == CMDMODE_NONE) tcmdlineinit(); else tcmdlinehide();
5370 // [show|hide]
5371 static void cmdCursor (const char *cmdname, char *argstr) {
5372 if (term != NULL) {
5373 char *s;
5375 if (iniParseArguments(argstr, "s!-", &s) != NULL) {
5376 tcmdlinemsg((term->c.state&CURSOR_HIDE) ? "cursor is hidden" : "cursor is visible");
5377 } else {
5378 if (strcasecmp(s, "show") == 0) term->c.state &= ~CURSOR_HIDE;
5379 else if (strcasecmp(s, "hide") == 0) term->c.state |= CURSOR_HIDE;
5380 term->wantRedraw = 1;
5386 static void cmdResetAttrs (const char *cmdname, char *argstr) {
5387 if (term != NULL) {
5388 term->c.attr.attr &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
5389 term->c.attr.attr |= ATTR_DEFFG | ATTR_DEFBG;
5390 term->c.attr.fg = term->deffg;
5391 term->c.attr.bg = term->defbg;
5396 static void cmdResetCharset (const char *cmdname, char *argstr) {
5397 if (term != NULL) {
5398 term->mode &= ~(MODE_GFX0|MODE_GFX1);
5399 term->charset = MODE_GFX0;
5404 // [norm|alt]
5405 static void cmdScreen (const char *cmdname, char *argstr) {
5406 if (term != NULL) {
5407 char *s;
5409 if (iniParseArguments(argstr, "s!-", &s) != NULL) {
5410 tcmdlinemsg(IS_SET(MODE_ALTSCREEN) ? "screen: alt" : "screen: norm");
5411 } else {
5412 if (strcasecmp(s, "norm") == 0) {
5413 if (IS_SET(MODE_ALTSCREEN)) tswapscreen();
5414 } else if (strcasecmp(s, "alt") == 0) {
5415 if (!IS_SET(MODE_ALTSCREEN)) tswapscreen();
5422 // [on|off]
5423 static void cmdMouseReports (const char *cmdname, char *argstr) {
5424 if (term != NULL) {
5425 int b;
5427 if (iniParseArguments(argstr, "b", &b) != NULL) {
5428 tcmdlinemsg(IS_SET(MODE_MOUSE) ? "mouse reports are on" : "mouse reports are off");
5429 } else {
5430 if (b) term->mode |= MODE_MOUSEBTN; else term->mode &= ~MODE_MOUSEBTN;
5436 static int cmd_parseIntArg (const char *fmt, char *argstr, int *b, int *global, int *toggle) {
5437 while (argstr[0]) {
5438 char *s = NULL;
5440 if (iniParseArguments(argstr, "R-", &argstr) != NULL) break;
5441 if (!argstr[0]) break;
5443 if (iniParseArguments(argstr, "s!-R-", &s, &argstr) != NULL) break;
5444 if (global && tolower(s[0]) == 'g') {
5445 *global = 1;
5446 } else if (toggle && tolower(s[0]) == 't') {
5447 *toggle = 1;
5448 } else {
5449 if (!b || iniParseArguments(s, fmt, b) != NULL) return -1;
5452 return 0;
5456 static void cmdAudibleBell (const char *cmdname, char *argstr) {
5457 int b = -1, toggle = 0, global = 0;
5459 if (term == NULL) return;
5460 if (cmd_parseIntArg("b", argstr, &b, &global, &toggle) != 0) return;
5462 if (b == -1) {
5463 tcmdlinemsg((global ? opt_audiblebell : (term->belltype&BELL_AUDIO)) ? "AudibleBell: 1" : "AudibleBell: 0");
5464 } else {
5465 if (toggle) {
5466 if (global) opt_audiblebell = !opt_audiblebell; else term->belltype ^= BELL_AUDIO;
5467 } else {
5468 if (global) opt_audiblebell = b; else term->belltype = (term->belltype&~BELL_AUDIO)|(b!=0?BELL_AUDIO:0);
5474 static void cmdUrgentBell (const char *cmdname, char *argstr) {
5475 int b = -1, toggle = 0, global = 0;
5477 if (term == NULL) return;
5478 if (cmd_parseIntArg("b", argstr, &b, &global, &toggle) != 0) return;
5480 if (b == -1) {
5481 tcmdlinemsg((global ? opt_urgentbell : (term->belltype&BELL_URGENT)) ? "UrgentBell: 1" : "UrgentBell: 0");
5482 } else {
5483 if (toggle) {
5484 if (global) opt_urgentbell = !opt_urgentbell; else term->belltype ^= BELL_URGENT;
5485 } else {
5486 if (global) opt_urgentbell = b; else term->belltype = (term->belltype&~BELL_URGENT)|(b!=0?BELL_URGENT:0);
5492 static void cmdMonochrome (const char *cmdname, char *argstr) {
5493 int b = -1, global = 0;
5495 if (term == NULL) return;
5496 if (cmd_parseIntArg("i{0,3}", argstr, &b, &global, NULL) != 0) return;
5498 if (b == -1) {
5499 char buf[32];
5501 b = (global ? globalBW : term->blackandwhite);
5502 sprintf(buf, "Monochrome: %d", b);
5503 tcmdlinemsg(buf);
5504 } else {
5505 if (global) {
5506 if (b == 3) tcmdlinemsg("Monochrome-global can't be '3'!");
5507 else globalBW = b;
5508 } else {
5509 term->blackandwhite = b;
5512 tfulldirt();
5513 updateTabBar = 1;
5517 static void cmdCursorBlink (const char *cmdname, char *argstr) {
5518 int b = -1, global = 0;
5520 if (term == NULL) return;
5521 if (cmd_parseIntArg("i{0,10000}", argstr, &b, &global, NULL) != 0) return;
5523 if (b == -1) {
5524 char buf[32];
5526 b = (global ? opt_cursorBlink : term->curblink);
5527 sprintf(buf, "CursorBlink: %d", b);
5528 tcmdlinemsg(buf);
5529 } else {
5530 if (global) {
5531 opt_cursorBlink = b;
5532 } else {
5533 term->curblink = b;
5534 term->curbhidden = 0;
5537 tfulldirt();
5538 updateTabBar = 1;
5542 static void cmdCursorBlinkInactive (const char *cmdname, char *argstr) {
5543 int b = -1, global = 0, toggle = 0, *iptr;
5545 if (term == NULL) return;
5546 if (cmd_parseIntArg("b", argstr, &b, &global, &toggle) != 0) return;
5548 iptr = (global ? &opt_cursorBlinkInactive : &term->curblinkinactive);
5549 if (b != -1) {
5550 if (toggle) *iptr = !(*iptr); else *iptr = b;
5551 draw(0);
5556 static void cmdIgnoreClose (const char *cmdname, char *argstr) {
5557 int b = -666;
5559 if (term == NULL) return;
5560 if (cmd_parseIntArg("i{-1,1}", argstr, &b, NULL, NULL) != 0) return;
5562 if (b == -666) {
5563 char buf[32];
5565 sprintf(buf, "IgnoreClose: %d", opt_ignoreclose);
5566 tcmdlinemsg(buf);
5567 } else {
5568 opt_ignoreclose = b;
5573 static void cmdMaxHistory (const char *cmdname, char *argstr) {
5574 int b = -1, global = 0;
5576 if (term == NULL) return;
5577 if (cmd_parseIntArg("i{0,65535}", argstr, &b, &global, NULL) != 0) return;
5579 if (b == -1) {
5580 char buf[32];
5582 sprintf(buf, "MaxHistory: %d", (global?opt_maxhistory:term->maxhistory));
5583 tcmdlinemsg(buf);
5584 } else {
5585 if (!global) tadjustmaxhistory(b); else opt_maxhistory = b;
5590 static const Command commandList[] = {
5591 {"PastePrimary", cmdPastePrimary},
5592 {"PasteSecondary", cmdPasteSecondary},
5593 {"PasteClipboard", cmdPasteClipboard},
5594 {"exec", cmdExec},
5595 {"NewTab", cmdNewTab}, // 'noswitch' 'next' 'prev' 'first' 'last'
5596 {"CloseTab", cmdCloseTab},
5597 {"KillTab", cmdKillTab},
5598 {"SwitchToTab", cmdSwitchToTab}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5599 {"MoveTabTo", cmdMoveTabTo}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5600 {"defaultfg", cmdDefaultFG},
5601 {"defaultbg", cmdDefaultBG},
5602 {"ScrollHistoryLineUp", cmdScrollHistoryLineUp},
5603 {"ScrollHistoryPageUp", cmdScrollHistoryPageUp},
5604 {"ScrollHistoryLineDown", cmdScrollHistoryLineDown},
5605 {"ScrollHistoryPageDown", cmdScrollHistoryPageDown},
5606 {"ScrollHistoryTop", cmdScrollHistoryTop},
5607 {"ScrollHistoryBottom", cmdScrollHistoryBottom},
5608 {"UTF8Locale", cmdUTF8Locale}, // 'on', 'off'
5609 {"AudibleBell", cmdAudibleBell},
5610 {"UrgentBell", cmdUrgentBell},
5611 {"CommandMode", cmdCommandMode},
5612 {"Cursor", cmdCursor},
5613 {"ResetAttrs", cmdResetAttrs},
5614 {"ResetCharset", cmdResetCharset},
5615 {"Screen", cmdScreen},
5616 {"MouseReports", cmdMouseReports},
5617 {"Monochrome", cmdMonochrome},
5618 {"Mono", cmdMonochrome},
5619 {"CursorBlink", cmdCursorBlink},
5620 {"CursorBlinkInactive", cmdCursorBlinkInactive},
5621 {"IgnoreClose", cmdIgnoreClose},
5622 {"MaxHistory", cmdMaxHistory},
5624 {"term", cmdTermName},
5625 {"title", cmdWinTitle},
5626 {"tabsize", cmdTabSize},
5627 {"defaultcursorfg", cmdDefaultCursorFG},
5628 {"defaultcursorbg", cmdDefaultCursorBG},
5629 {"defaultinactivecursorfg", cmdDefaultInactiveCursorFG},
5630 {"defaultinactivecursorbg", cmdDefaultInactiveCursorBG},
5631 {"defaultboldfg", cmdDefaultBoldFG},
5632 {"defaultunderlinefg", cmdDefaultUnderlineFG},
5634 {NULL, NULL}
5638 static const char *findCommandCompletion (const char *str, int slen, const char *prev) {
5639 const char *res = NULL;
5640 int phit = 0;
5642 if (slen < 1) return NULL;
5643 for (int f = 0; commandList[f].name != NULL; ++f) {
5644 if (strlen(commandList[f].name) >= slen && strncasecmp(commandList[f].name, str, slen) == 0) {
5645 if (prev == NULL || phit) return commandList[f].name;
5646 if (strcasecmp(commandList[f].name, prev) == 0) phit = 1;
5647 if (res == NULL) res = commandList[f].name;
5650 return res;
5654 // !0: NewTab command
5655 static int executeCommand (const char *str, int slen) {
5656 const char *e;
5657 char *cmdname;
5658 int cmdfound = 0;
5660 if (str == NULL) return 0;
5661 if (slen < 0) slen = strlen(str);
5663 for (int f = 0; f < slen; ++f) if (!str[f]) { slen = f; break; }
5665 while (slen > 0 && isspace(*str)) { ++str; --slen; }
5666 if (slen < 1 || !str[0]) return 0;
5668 for (e = str; slen > 0 && !isspace(*e); ++e, --slen) ;
5670 if (e-str > 127) return 0;
5671 cmdname = alloca(e-str+8);
5672 if (cmdname == NULL) return 0;
5673 memcpy(cmdname, str, e-str);
5674 cmdname[e-str] = 0;
5675 if (opt_disabletabs && strcasecmp(cmdname, "NewTab") == 0) return 1;
5677 while (slen > 0 && isspace(*e)) { ++e; --slen; }
5678 //FIXME: ugly copypaste!
5680 for (int f = 0; commandList[f].name != NULL; ++f) {
5681 if (strcasecmp(commandList[f].name, cmdname) == 0) {
5682 char *left = calloc(slen+2, 1);
5684 if (left != NULL) {
5685 if (slen > 0) memcpy(left, e, slen);
5686 //fprintf(stderr, "command: [%s]; args: [%s]\n", cmdname, left);
5687 commandList[f].fn(cmdname, left);
5688 free(left);
5690 cmdfound = 1;
5691 break;
5695 if (!cmdfound) {
5696 char *left = calloc(slen+2, 1);
5698 if (left != NULL) {
5699 if (slen > 0) memcpy(left, e, slen);
5700 processMiscCmds(cmdname, left);
5701 free(left);
5705 return 0;
5710 static const char *cmdpSkipStr (const char *str) {
5711 while (*str && isspace(*str)) ++str;
5712 if (str[0] && str[0] != ';') {
5713 char qch = ' ';
5715 while (*str) {
5716 if (*str == ';' && qch == ' ') break;
5717 if (qch != ' ' && *str == qch) { qch = ' '; ++str; continue; }
5718 if (*str == '"' || *str == '\'') {
5719 if (qch == ' ') qch = *str;
5720 ++str;
5721 continue;
5723 if (*str++ == '\\' && *str) ++str;
5726 return str;
5731 static void executeCommands (const char *str) {
5732 oldTerm = term;
5733 oldTermIdx = termidx;
5734 newTerm = NULL;
5735 newTermSwitch = 0;
5736 if (str == NULL) return;
5737 while (*str) {
5738 const char *ce;
5739 char qch;
5741 while (*str && isspace(*str)) ++str;
5742 if (!*str) break;
5743 if (*str == ';') { ++str; continue; }
5745 ce = str;
5746 qch = ' ';
5747 while (*ce) {
5748 if (*ce == ';' && qch == ' ') break;
5749 if (qch != ' ' && *ce == qch) { qch = ' '; ++ce; continue; }
5750 if (*ce == '"' || *ce == '\'') {
5751 if (qch == ' ') qch = *ce;
5752 ++ce;
5753 continue;
5755 if (*ce++ == '\\' && *ce) ++ce;
5758 if (executeCommand(str, ce-str)) break;
5759 if (*ce) str = ce+1; else break;
5761 flushNewTerm();
5762 switchToTerm(oldTermIdx, 1);
5766 ////////////////////////////////////////////////////////////////////////////////
5767 int main (int argc, char *argv[]) {
5768 char *configfile = NULL;
5769 char *runcmd = NULL;
5771 //dbgLogInit();
5773 for (int f = 1; f < argc; f++) {
5774 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
5775 if (strcmp(argv[f], "-into") == 0) { ++f; continue; }
5776 if (strcmp(argv[f], "-embed") == 0) { ++f; continue; }
5777 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
5778 case 'e': f = argc+1; break;
5779 case 't':
5780 case 'c':
5781 case 'w':
5782 case 'b':
5783 case 'R':
5784 ++f;
5785 break;
5786 case 'T':
5787 if (++f < argc) {
5788 free(opt_term);
5789 opt_term = strdup(argv[f]);
5790 opt_term_locked = 1;
5792 break;
5793 case 'C':
5794 if (++f < argc) {
5795 if (configfile != NULL) free(configfile);
5796 configfile = strdup(argv[f]);
5798 break;
5799 case 'l':
5800 if (++f < argc) cliLocale = argv[f];
5801 break;
5802 case 'S': // single-tab mode
5803 opt_disabletabs = 1;
5804 break;
5805 case 'v':
5806 case 'h':
5807 default:
5808 fprintf(stderr, "%s", USAGE);
5809 exit(EXIT_FAILURE);
5813 initDefaultOptions();
5814 if (configfile == NULL) {
5815 const char *home = getenv("HOME");
5817 if (home != NULL) {
5818 configfile = SPrintf("%s/.sterm.rc", home);
5819 if (loadConfig(configfile) == 0) goto cfgdone;
5820 free(configfile); configfile = NULL;
5822 configfile = SPrintf("%s/.config/sterm.rc", home);
5823 if (loadConfig(configfile) == 0) goto cfgdone;
5824 free(configfile); configfile = NULL;
5827 configfile = SPrintf("/etc/sterm.rc");
5828 if (loadConfig(configfile) == 0) goto cfgdone;
5829 free(configfile); configfile = NULL;
5831 configfile = SPrintf("/etc/sterm/sterm.rc");
5832 if (loadConfig(configfile) == 0) goto cfgdone;
5833 free(configfile); configfile = NULL;
5835 configfile = SPrintf("./.sterm.rc");
5836 if (loadConfig(configfile) == 0) goto cfgdone;
5837 free(configfile); configfile = NULL;
5838 // no config
5839 } else {
5840 if (loadConfig(configfile) < 0) die("config file '%s' not found!", configfile);
5842 cfgdone:
5843 if (configfile != NULL) free(configfile); configfile = NULL;
5845 for (int f = 1; f < argc; f++) {
5846 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
5847 if (strcmp(argv[f], "-into") == 0 || strcmp(argv[f], "-embed") == 0) {
5848 if (opt_embed) free(opt_embed);
5849 opt_embed = strdup(argv[f]);
5850 continue;
5852 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
5853 case 't':
5854 if (++f < argc) {
5855 free(opt_title);
5856 opt_title = strdup(argv[f]);
5858 break;
5859 case 'c':
5860 if (++f < argc) {
5861 free(opt_class);
5862 opt_class = strdup(argv[f]);
5864 break;
5865 case 'w':
5866 if (++f < argc) {
5867 if (opt_embed) free(opt_embed);
5868 opt_embed = strdup(argv[f]);
5870 break;
5871 case 'R':
5872 if (++f < argc) runcmd = argv[f];
5873 break;
5874 case 'e':
5875 /* eat every remaining arguments */
5876 if (++f < argc) opt_cmd = &argv[f];
5877 f = argc+1;
5878 case 'T':
5879 case 'C':
5880 case 'l':
5881 ++f;
5882 break;
5883 case 'S':
5884 break;
5885 case 'v':
5886 case 'h':
5887 default:
5888 fprintf(stderr, "%s", USAGE);
5889 exit(EXIT_FAILURE);
5893 setenv("TERM", opt_term, 1);
5894 mclock_init();
5895 setlocale(LC_ALL, "");
5896 initLCConversion();
5897 updateTabBar = 1;
5898 termidx = 0;
5899 term = termalloc();
5900 if (term->execcmd != NULL) { free(term->execcmd); term->execcmd = NULL; }
5901 tinitialize(80, 25);
5902 if (ttynew(term) != 0) die("can't run process");
5903 opt_cmd = NULL;
5904 xinit();
5905 selinit();
5906 if (runcmd != NULL) executeCommands(runcmd);
5907 run();
5908 return 0;