new command: 'tabcount'. # of tabs in tabbar now can be adjusted in runtime
[k8sterm.git] / src / sterm.c
blobd63ee55eff661a6ef7d21e4a85e8e2984d249a6c
1 /* See LICENSE for licence details. */
2 #define VERSION "0.3.1.beta5"
4 #ifdef _XOPEN_SOURCE
5 # undef _XOPEN_SOURCE
6 #endif
7 #define _XOPEN_SOURCE 600
9 #include <alloca.h>
10 #include <ctype.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <iconv.h>
14 #include <limits.h>
15 #include <locale.h>
16 #include <pty.h>
17 #include <stdarg.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <strings.h>
22 #include <signal.h>
23 #include <sys/ioctl.h>
24 #include <sys/select.h>
25 #include <sys/stat.h>
26 #include <sys/time.h>
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #include <time.h>
30 #include <unistd.h>
31 #include <X11/Xatom.h>
32 #include <X11/Xlib.h>
33 #include <X11/Xutil.h>
34 #include <X11/cursorfont.h>
35 #include <X11/keysym.h>
37 //#include "dbglog.h"
40 // uncomment the following to use XCreateGlyphCursor() instead of XCreatePixmapCursor()
41 //#define BLANKPTR_USE_GLYPH_CURSOR
44 //#define PASTE_SELECTION_DEBUG
46 //#define DUMP_KEYSYMS
48 //#define KEYPAD_DUMP
50 //#define DUMP_PROG_OUTPUT
51 //#define DUMP_PROG_INPUT
53 //#define DUMP_IO_READ
54 //#define DUMP_IO_WRITE
56 #if defined(DUMP_IO_READ) || defined(DUMP_IO_WRITE)
57 # define DUMP_IO
58 #endif
60 #ifdef KEYPAD_DUMP
61 # define DUMP_KEYPAD_SWITCH(strflag,strstate) do { fprintf(stderr, "KEYPAD %s (%s)\n", (strstate), (strflag)); } while (0)
62 #else
63 # define DUMP_KEYPAD_SWITCH(strflag,strstate) ((void)(sizeof(strflag)+sizeof(strstate)))
64 #endif
67 ////////////////////////////////////////////////////////////////////////////////
68 #define USAGE \
69 "sterm " VERSION " (c) 2010-2012 st engineers and Ketmar // Vampire Avalon\n" \
70 "usage: sterm [-t title] [-c class] [-w windowid] [-T termname] [-C config_file] [-l langiconv] [-S] [-v] [-R stcmd] [-e command...]\n"
73 ////////////////////////////////////////////////////////////////////////////////
74 #define FONT "-*-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"
75 #define FONTBOLD "-*-fixed-bold-r-normal-*-18-*-*-*-*-*-*-*"
76 #define FONTTAB "-*-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*"
79 /* Default shell to use if SHELL is not set in the env */
80 #define SHELL "/bin/sh"
83 /* Terminal colors (16 first used in escape sequence) */
84 static const char *defcolornames[] = {
85 #if 1
86 /* 8 normal colors */
87 "black",
88 "red3",
89 "green3",
90 "yellow3",
91 "blue2",
92 "magenta3",
93 "cyan3",
94 "gray90",
95 /* 8 bright colors */
96 "gray50",
97 "red",
98 "green",
99 "yellow",
100 "#5c5cff",
101 "magenta",
102 "cyan",
103 "white",
104 #else
105 /* 8 normal colors */
106 "#000000",
107 "#b21818",
108 "#18b218",
109 "#b26818",
110 "#1818b2",
111 "#b218b2",
112 "#18b2b2",
113 "#b2b2b2",
114 /* 8 bright colors */
115 "#686868",
116 "#ff5454",
117 "#54ff54",
118 "#ffff54",
119 "#5454ff",
120 "#ff54ff",
121 "#54ffff",
122 "#ffffff",
123 #endif
127 /* more colors can be added after 255 to use with DefaultXX */
128 static const char *defextcolornames[] = {
129 "#cccccc", /* 256 */
130 "#333333", /* 257 */
131 /* root terminal fg and bg */
132 "#809a70", /* 258 */
133 "#002000", /* 259 */
134 /* bold and underline */
135 "#00afaf", /* 260 */
136 "#00af00", /* 261 */
140 /* Default colors (colorname index) foreground, background, cursor, unfocused cursor */
141 #define DEFAULT_FG (7)
142 #define DEFAULT_BG (0)
143 #define DEFAULT_CS (256)
144 #define DEFAULT_UCS (257)
146 #define TNAME "xterm"
148 /* double-click timeout (in milliseconds) between clicks for selection */
149 #define DOUBLECLICK_TIMEOUT (300)
150 #define TRIPLECLICK_TIMEOUT (2*DOUBLECLICK_TIMEOUT)
151 //#define SELECT_TIMEOUT 20 /* 20 ms */
152 #define DRAW_TIMEOUT 18 /* 18 ms */
154 #define TAB (8)
157 ////////////////////////////////////////////////////////////////////////////////
158 #define MAX_COLOR (511)
160 /* XEMBED messages */
161 #define XEMBED_FOCUS_IN (4)
162 #define XEMBED_FOCUS_OUT (5)
165 /* Arbitrary sizes */
166 #define ESC_TITLE_SIZ (256)
167 #define ESC_BUF_SIZ (256)
168 #define ESC_ARG_SIZ (16)
169 #define DRAW_BUF_SIZ (2048)
170 #define UTF_SIZ (4)
171 #define OBUFSIZ (256)
172 #define WBUFSIZ (256)
175 /* masks for key translation */
176 #define XK_NO_MOD (UINT_MAX)
177 #define XK_ANY_MOD (0)
180 /* misc utility macros */
181 //#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000+(t1.tv_usec-t2.tv_usec)/1000)
182 #define SERRNO strerror(errno)
183 #define MIN(a, b) ((a) < (b) ? (a) : (b))
184 #define MAX(a, b) ((a) < (b) ? (b) : (a))
185 #define LEN(a) (sizeof(a)/sizeof(a[0]))
186 #define DEFAULT(a, b) (a) = ((a) ? (a) : (b))
187 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
188 #define LIMIT(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x))
189 #define ATTRCMP(a, b) ((a).attr != (b).attr || (a).fg != (b).fg || (a).bg != (b).bg)
190 #define IS_SET(flag) (term->mode&(flag))
191 #define X2COL(x) ((x)/xw.cw)
192 #define Y2ROW(y) ((y-(opt_tabposition==1 ? xw.tabheight : 0))/xw.ch-(term!=NULL ? term->topline : 0))
193 #define IS_GFX(mode) ((mode)&ATTR_GFX)
196 ////////////////////////////////////////////////////////////////////////////////
197 enum {
198 BELL_AUDIO = 0x01,
199 BELL_URGENT = 0x02
202 enum {
203 CMDMODE_NONE,
204 CMDMODE_INPUT,
205 CMDMODE_MESSAGE
208 enum glyph_attribute {
209 ATTR_NULL = 0x00,
210 ATTR_REVERSE = 0x01,
211 ATTR_UNDERLINE = 0x02,
212 ATTR_BOLD = 0x04,
213 ATTR_GFX = 0x08,
214 ATTR_DEFFG = 0x10,
215 ATTR_DEFBG = 0x20,
218 enum cursor_movement {
219 CURSOR_UP,
220 CURSOR_DOWN,
221 CURSOR_LEFT,
222 CURSOR_RIGHT,
223 CURSOR_SAVE,
224 CURSOR_LOAD
227 enum cursor_state {
228 CURSOR_DEFAULT = 0x00,
229 CURSOR_HIDE = 0x01,
230 CURSOR_WRAPNEXT = 0x02
233 enum glyph_state {
234 GLYPH_SET = 0x01, /* for selection only */
235 GLYPH_DIRTY = 0x02,
236 GLYPH_WRAP = 0x10, /* can be set for the last line glyph */
240 enum term_mode {
241 MODE_WRAP = 0x01,
242 MODE_INSERT = 0x02,
243 MODE_APPKEYPAD = 0x04,
244 MODE_ALTSCREEN = 0x08,
245 MODE_CRLF = 0x10,
246 MODE_MOUSEBTN = 0x20,
247 MODE_MOUSEMOTION = 0x40,
248 MODE_MOUSE = 0x20|0x40,
249 MODE_REVERSE = 0x80,
250 MODE_BRACPASTE = 0x100,
251 MODE_FOCUSEVT = 0x200,
252 MODE_DISPCTRL = 0x400, //TODO: not implemented yet
253 MODE_GFX0 = 0x1000,
254 MODE_GFX1 = 0x2000,
257 enum escape_state {
258 ESC_START = 0x01,
259 ESC_CSI = 0x02,
260 ESC_OSC = 0x04,
261 ESC_TITLE = 0x08,
262 ESC_ALTCHARSET = 0x10,
263 ESC_HASH = 0x20,
264 ESC_PERCENT = 0x40,
265 ESC_ALTG1 = 0x80,
268 enum window_state {
269 WIN_VISIBLE = 0x01,
270 WIN_REDRAW = 0x02,
271 WIN_FOCUSED = 0x04,
274 /* bit macro */
275 #undef B0
276 enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 };
279 ////////////////////////////////////////////////////////////////////////////////
280 typedef unsigned char uchar;
281 typedef unsigned int uint;
282 typedef unsigned long ulong;
283 typedef unsigned short ushort;
286 typedef struct __attribute__((packed)) {
287 char c[UTF_SIZ]; /* character code */
288 uchar attr; /* attribute flags */
289 ushort fg; /* foreground */
290 ushort bg; /* background */
291 uchar state; /* state flags */
292 } Glyph;
295 typedef Glyph *Line;
297 typedef struct {
298 Glyph attr; /* current char attributes */
299 int x;
300 int y;
301 char state;
302 } TCursor;
305 /* CSI Escape sequence structs */
306 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
307 typedef struct {
308 char buf[ESC_BUF_SIZ]; /* raw string */
309 int len; /* raw string length */
310 char priv;
311 int arg[ESC_ARG_SIZ];
312 int narg; /* nb of args */
313 char mode;
314 } CSIEscape;
317 /* Purely graphic info */
318 typedef struct {
319 Display *dpy;
320 Colormap cmap;
321 Window win;
322 Cursor cursor;
323 Cursor blankPtr;
324 Atom xembed;
325 XIM xim;
326 XIC xic;
327 int scr;
328 int w; /* window width */
329 int h; /* window height */
330 int bufw; /* pixmap width */
331 int bufh; /* pixmap height */
332 int ch; /* char height */
333 int cw; /* char width */
334 char state; /* focus, redraw, visible */
336 int tch; /* tab text char height */
337 Pixmap pictab;
338 int tabheight;
339 //struct timeval lastdraw;
340 } XWindow;
343 /* TODO: use better name for vars... */
344 typedef struct {
345 int mode;
346 int bx, by;
347 int ex, ey;
348 struct { int x, y; } b, e;
349 char *clip;
350 Atom xtarget;
351 int tclick1;
352 int tclick2;
353 } Selection;
356 /* Drawing Context */
357 typedef struct {
358 ulong *ncol; // normal colors
359 ulong *bcol; // b/w colors
360 ulong *gcol; // green colors
361 ulong *clrs[3];
362 GC gc;
363 struct {
364 int ascent;
365 int descent;
366 short lbearing;
367 short rbearing;
368 XFontSet set;
369 Font fid;
370 } font[3];
371 } DC;
374 typedef void (*CmdLineExecFn) (int cancelled);
377 #define CMDLINE_SIZE (256)
379 /* Internal representation of the screen */
380 typedef struct Term {
381 int cmdfd;
382 int dead;
383 int exitcode;
384 int needConv; /* 0: utf-8 locale */
385 int belltype;
386 int blackandwhite;
388 int curblink;
389 int curbhidden;
390 int lastBlinkTime;
391 int curblinkinactive;
393 int row; /* nb row */
394 int col; /* nb col */
395 int topline; /* top line for drawing (0: no history; 1: show one history line; etc) */
396 int linecount; /* full, with history */
397 int maxhistory;/* max history lines; 0: none; <0: infinite */
398 Line *line; /* screen */
399 Line *alt; /* alternate screen */
400 char *dirty; /* dirtyness of lines */
401 TCursor c; /* cursor */
402 int top; /* top scroll limit */
403 int bot; /* bottom scroll limit */
404 int mode; /* terminal mode flags */
405 int mousemode; /* mouse mode: 1000, 1005, 1006, 1015 */
406 int esc; /* escape state flags */
407 int charset; /* 0 or 1 */
409 TCursor csaved; /* saved cursor info */
410 // old cursor position
411 int oldcx;
412 int oldcy;
414 char title[ESC_TITLE_SIZ+1];
415 int titlelen;
417 int mouseob;
418 int mouseox;
419 int mouseoy;
421 char obuf[OBUFSIZ];
422 #ifdef DUMP_PROG_OUTPUT
423 int xobuflen;
424 #endif
425 int obuflen;
427 char ubuf[UTF_SIZ];
428 int ubufpos;
430 char drawbuf[DRAW_BUF_SIZ];
432 char wrbuf[WBUFSIZ];
433 int wrbufsize;
434 int wrbufused;
435 int wrbufpos;
437 CSIEscape escseq;
438 Selection sel;
439 pid_t pid;
440 int lastDrawTime;
442 char *execcmd;
444 Pixmap picbuf;
445 int picbufw;
446 int picbufh;
448 ushort deffg;
449 ushort defbg;
451 int wantRedraw;
453 int lastActiveTime;
455 int cmdMode;
456 char cmdline[UTF_SIZ*CMDLINE_SIZE];
457 int cmdreslen; // byte length of 'reserved' (read-only) part
458 int cmdofs;
459 char cmdc[UTF_SIZ+1];
460 int cmdcl;
461 int cmdtabpos;
462 const char *cmdprevc;
463 CmdLineExecFn cmdexecfn;
464 } Term;
467 ////////////////////////////////////////////////////////////////////////////////
468 /* Globals */
469 static ushort *unimap = NULL; // 0 or 65535 items; 127: special; high bit set: use alt(gfx) mode; 0: empty
471 static char **opt_cmd = NULL;
472 static char *opt_title = NULL;
473 static char *opt_embed = NULL;
474 static char *opt_class = NULL;
475 static char *opt_term = NULL;
476 static char *opt_fontnorm = NULL;
477 static char *opt_fontbold = NULL;
478 static char *opt_fonttab = NULL;
479 static char *opt_shell = NULL;
480 static char *opt_colornames[512];
481 static int opt_term_locked = 0;
482 static int opt_doubleclick_timeout = DOUBLECLICK_TIMEOUT;
483 static int opt_tripleclick_timeout = TRIPLECLICK_TIMEOUT;
484 static int opt_tabsize = TAB;
485 static int defaultFG = DEFAULT_FG;
486 static int defaultBG = DEFAULT_BG;
487 static int defaultCursorFG = 0;
488 static int defaultCursorBG = DEFAULT_CS;
489 static int defaultCursorInactiveFG = 0;
490 static int defaultCursorInactiveBG = DEFAULT_UCS;
491 static int defaultBoldFG = -1;
492 static int defaultUnderlineFG = -1;
493 static int normalTabFG = 258;
494 static int normalTabBG = 257;
495 static int activeTabFG = 258;
496 static int activeTabBG = 0;
497 static int opt_maxhistory = 512;
498 static int opt_ptrblank = 2000; // delay; 0: never
499 static int opt_tabcount = 6;
500 static int opt_tabposition = 0; // 0: bottom; 1: top
501 static int opt_drawtimeout = DRAW_TIMEOUT;
502 static int opt_disabletabs = 0;
503 static int opt_audiblebell = 1;
504 static int opt_urgentbell = 1;
505 static int opt_cursorBlink = 0;
506 static int opt_cursorBlinkInactive = 0;
507 static int opt_drawunderline = 1;
508 static int opt_ignoreclose = 0;
509 static int opt_maxdrawtimeout = 3000;
510 static int ptrBlanked = 0;
511 static int ptrLastMove = 0;
512 static int globalBW = 0;
514 static Term **term_array = NULL;
515 static int term_count = 0;
516 static int term_array_size = 0;
517 static Term *term; // current terminal
518 static int termidx; // current terminal index; DON'T RELAY ON IT!
519 static int updateTabBar;
520 static int lastDrawTime = 0;
521 static char *lastSelStr = NULL;
522 //static int lastSelLength = 0;
524 static int firstVisibleTab = 0;
526 static int exitcode = 0;
527 static int closeRequestComes = 0;
529 static DC dc;
530 static XWindow xw;
532 static Atom XA_VT_SELECTION;
533 static Atom XA_CLIPBOARD;
534 static Atom XA_UTF8;
535 static Atom XA_TARGETS;
536 static Atom XA_NETWM_NAME;
537 static Atom XA_WM_DELETE_WINDOW;
540 ////////////////////////////////////////////////////////////////////////////////
541 typedef struct {
542 KeySym src;
543 KeySym dst;
544 } KeyTransDef;
547 static KeyTransDef *keytrans = NULL;
548 static int keytrans_size = 0;
549 static int keytrans_used = 0;
552 typedef struct {
553 KeySym key;
554 uint mask;
555 int kp;
556 char *str;
557 } KeyInfoDef;
560 static KeyInfoDef *keybinds = NULL;
561 static int keybinds_size = 0;
562 static int keybinds_used = 0;
564 static KeyInfoDef *keymap = NULL;
565 static int keymap_size = 0;
566 static int keymap_used = 0;
569 ////////////////////////////////////////////////////////////////////////////////
570 static void executeCommands (const char *str);
571 static const char *findCommandCompletion (const char *str, int slen, const char *prev);
573 static void ttyresize (void);
574 static void tputc (const char *c); // `c` is utf-8
575 static void ttywrite (const char *s, size_t n);
576 static void tsetdirt (int top, int bot);
577 static void tfulldirt (void);
579 static void xseturgency (int add);
580 static void xfixsel (void);
581 static void xblankPointer (void);
582 static void xunblankPointer (void);
584 static void draw (int forced);
586 static void tcmdput (const char *s, int len);
589 static inline void ttywritestr (const char *s) { if (s != NULL && s[0]) ttywrite(s, strlen(s)); }
592 static inline ulong getColor (int idx) {
593 if (globalBW && (term == NULL || !term->blackandwhite)) return dc.clrs[globalBW][idx];
594 if (term != NULL) return dc.clrs[term->blackandwhite%3][idx];
595 return dc.clrs[0][idx];
597 if (globalBW) return dc.bcol[idx];
598 if (term != NULL) {
599 return (term->blackandwhite ? dc.bcol[idx] : dc.ncol[idx]);
601 return dc.ncol[idx];
606 ////////////////////////////////////////////////////////////////////////////////
608 static void trimstr (char *s) {
609 char *e;
611 while (*s && isspace(*s)) ++s;
612 for (e = s+strlen(s); e > s; --e) if (!isspace(e[-1])) break;
613 if (e <= s) *s = 0; else *e = 0;
617 // parse the argument list
618 // return error message or NULL
619 // format:
620 // '*': skip
621 // 's': string (char *)
622 // 'i': integer (int *)
623 // 'b': boolean (int *)
624 // '|': optional arguments follows
625 // '.': stop parsing, ignore rest
626 // 'R': stop parsing, set rest ptr (char *)
627 // string modifiers (also for 'R'):
628 // '!' -- don't allow empty strings
629 // '-' -- trim spaces
630 // int modifiers:
631 // {lo,hi}
632 // {,hi}
633 // {lo}
634 // WARNING! `line` will be modified!
635 // WARNING! string pointers will point INSIDE `line`, so don't discard it!
636 // UGLY! REWRITE!
637 const char *iniParseArguments (char *line, const char *fmt, ...) {
638 va_list ap;
639 int inOptional = 0;
641 if (line == NULL) return "alas";
642 trimstr(line);
643 va_start(ap, fmt);
644 while (*fmt) {
645 char spec = *fmt++, *args;
647 if (spec == '|') { inOptional = 1; continue; }
648 if (spec == '.') { va_end(ap); return NULL; }
650 while (*line && isspace(*line)) ++line;
651 if (*line == '#') *line = 0;
653 if (spec == 'R') {
654 char **p = va_arg(ap, char **);
655 int noempty = 0;
657 while (*fmt) {
658 if (*fmt == '!') { ++fmt; noempty = 1; }
659 else if (*fmt == '-') { ++fmt; trimstr(line); }
660 else break;
662 va_end(ap);
663 if (noempty && !line[0]) return "invalid empty arg";
664 if (p != NULL) *p = line;
665 return NULL;
668 if (!line[0]) {
669 // end of line, stop right here
670 va_end(ap);
671 if (!inOptional) return "out of args";
672 return NULL;
675 args = line;
677 char *dest = args, qch = '#';
678 int n;
680 if (line[0] == '"' || line[0] == '\'') qch = *line++;
682 while (*line && *line != qch) {
683 if (qch == '#' && isspace(*line)) break;
685 if (*line == '\\') {
686 if (!line[1]) { va_end(ap); return "invalid escape"; }
687 switch (*(++line)) {
688 case 'n': *dest++ = '\n'; ++line; break;
689 case 'r': *dest++ = '\r'; ++line; break;
690 case 't': *dest++ = '\t'; ++line; break;
691 case 'a': *dest++ = '\a'; ++line; break;
692 case 'e': *dest++ = '\x1b'; ++line; break; // esc
693 case 's': *dest++ = ' '; ++line; break;
694 case 'x': // hex
695 ++line;
696 if (!isxdigit(*line)) { va_end(ap); return "invalid hex escape"; }
697 n = toupper(*line)-'0'; if (n > 9) n -= 7;
698 ++line;
699 if (isxdigit(*line)) {
700 int b = toupper(*line)-'0'; if (b > 9) b -= 7;
702 n = (n*16)+b;
703 ++line;
705 *dest++ = n;
706 break;
707 case '0': // octal
708 n = 0;
709 for (int f = 0; f < 4; ++f) {
710 if (*line < '0' || *line > '7') break;
711 n = (n*8)+(line[0]-'0');
712 if (n > 255) { va_end(ap); return "invalid oct escape"; }
713 ++line;
715 if (n == 0) { va_end(ap); return "invalid oct escape"; }
716 *dest++ = n;
717 break;
718 case '1'...'9': // decimal
719 n = 0;
720 for (int f = 0; f < 3; ++f) {
721 if (*line < '0' || *line > '9') break;
722 n = (n*8)+(line[0]-'0');
723 if (n > 255) { va_end(ap); return "invalid dec escape"; }
724 ++line;
726 if (n == 0) { va_end(ap); return "invalid oct escape"; }
727 *dest++ = n;
728 break;
729 default:
730 *dest++ = *line++;
731 break;
733 } else {
734 *dest++ = *line++;
737 if (qch != '#') {
738 if (*line != qch) return "unfinished string";
739 if (*line) ++line;
740 } else if (*line && *line != '#') ++line;
741 *dest = 0;
743 // now process and convert argument
744 switch (spec) {
745 case '*': /* skip */
746 break;
747 case 's': { /* string */
748 int noempty = 0, trim = 0;
749 char **p;
751 for (;;) {
752 if (*fmt == '!') { noempty = 1; ++fmt; }
753 else if (*fmt == '-') { trim = 1; ++fmt; }
754 else break;
757 if (trim) trimstr(args);
759 if (noempty && !args[0]) { va_end(ap); return "invalid empty string"; }
760 p = va_arg(ap, char **);
761 if (p != NULL) *p = args;
762 } break;
763 case 'i': /* int */
764 if (!args[0]) {
765 va_end(ap);
766 return "invalid integer";
767 } else {
768 int *p = va_arg(ap, int *);
769 long int n;
770 char *eptr;
772 trimstr(args);
773 n = strtol(args, &eptr, 0);
774 if (*eptr) { va_end(ap); return "invalid integer"; }
776 if (*fmt == '{') {
777 // check min/max
778 int minmax[2], haveminmax[2];
780 haveminmax[0] = haveminmax[1] = 0;
781 minmax[0] = minmax[1] = 0;
782 ++fmt;
783 for (int f = 0; f < 2; ++f) {
784 if (isdigit(*fmt) || *fmt == '-' || *fmt == '+') {
785 int neg = 0;
786 haveminmax[f] = 1;
787 if (*fmt == '-') neg = 1;
788 if (!isdigit(*fmt)) {
789 ++fmt;
790 if (!isdigit(*fmt)) { va_end(ap); return "invalid integer bounds"; }
792 while (isdigit(*fmt)) {
793 minmax[f] = minmax[f]*10+(fmt[0]-'0');
794 ++fmt;
796 if (neg) minmax[f] = -minmax[f];
797 //fprintf(stderr, "got: %d\n", minmax[f]);
799 if (*fmt == ',') {
800 if (f == 1) { va_end(ap); return "invalid integer bounds: extra comma"; }
801 // do nothing, we are happy
802 ++fmt;
803 } else if (*fmt == '}') {
804 // ok, done
805 break;
806 } else { va_end(ap); return "invalid integer bounds"; }
808 if (*fmt != '}') { va_end(ap); return "invalid integer bounds"; }
809 ++fmt;
811 //fprintf(stderr, "b: (%d,%d) (%d,%d)\n", haveminmax[0], minmax[0], haveminmax[1], minmax[1]);
812 if ((haveminmax[0] && n < minmax[0]) || (haveminmax[1] && n > minmax[1])) { va_end(ap); return "integer out of bounds"; }
815 if (p) *p = n;
817 break;
818 case 'b': { /* bool */
819 int *p = va_arg(ap, int *);
821 trimstr(args);
822 if (!args[0]) { va_end(ap); return "invalid boolean"; }
823 if (strcasecmp(args, "true") == 0 || strcasecmp(args, "on") == 0 ||
824 strcasecmp(args, "tan") == 0 || strcasecmp(args, "1") == 0) {
825 if (p) *p = 1;
826 } else if (strcasecmp(args, "false") == 0 || strcasecmp(args, "off") == 0 ||
827 strcasecmp(args, "ona") == 0 || strcasecmp(args, "0") == 0) {
829 if (p) *p = 0;
830 } else {
831 va_end(ap);
832 return "invalid boolean";
834 } break;
835 default:
836 va_end(ap);
837 return "invalid format specifier";
840 va_end(ap);
841 while (*line && isspace(*line)) ++line;
842 if (!line[0] || line[0] == '#') return NULL;
843 return "extra args";
847 ////////////////////////////////////////////////////////////////////////////////
848 // UTF-8
849 static int utf8decode (const char *s, ulong *u) {
850 uchar c;
851 int n, rtn;
853 rtn = 1;
854 c = *s;
855 if (~c & B7) { /* 0xxxxxxx */
856 *u = c;
857 return rtn;
858 } else if ((c & (B7|B6|B5)) == (B7|B6)) { /* 110xxxxx */
859 *u = c&(B4|B3|B2|B1|B0);
860 n = 1;
861 } else if ((c & (B7|B6|B5|B4)) == (B7|B6|B5)) { /* 1110xxxx */
862 *u = c&(B3|B2|B1|B0);
863 n = 2;
864 } else if ((c & (B7|B6|B5|B4|B3)) == (B7|B6|B5|B4)) { /* 11110xxx */
865 *u = c & (B2|B1|B0);
866 n = 3;
867 } else {
868 goto invalid;
870 ++s;
871 for (int f = n; f > 0; --f, ++rtn, ++s) {
872 c = *s;
873 if ((c & (B7|B6)) != B7) goto invalid; /* 10xxxxxx */
874 *u <<= 6;
875 *u |= c & (B5|B4|B3|B2|B1|B0);
877 if ((n == 1 && *u < 0x80) ||
878 (n == 2 && *u < 0x800) ||
879 (n == 3 && *u < 0x10000) ||
880 (*u >= 0xD800 && *u <= 0xDFFF)) {
881 goto invalid;
883 return rtn;
884 invalid:
885 *u = 0xFFFD;
886 return rtn;
890 static int utf8encode (ulong u, char *s) {
891 uchar *sp;
892 ulong uc;
893 int n;
895 sp = (uchar *)s;
896 uc = u&0x1fffff;
897 if (uc < 0x80) {
898 *sp = uc; /* 0xxxxxxx */
899 return 1;
900 } else if (uc < 0x800) {
901 *sp = (uc >> 6) | (B7|B6); /* 110xxxxx */
902 n = 1;
903 } else if (uc < 0x10000) {
904 *sp = (uc >> 12) | (B7|B6|B5); /* 1110xxxx */
905 n = 2;
906 } else if (uc <= 0x10FFFF) {
907 *sp = (uc >> 18) | (B7|B6|B5|B4); /* 11110xxx */
908 n = 3;
909 } else {
910 goto invalid;
912 ++sp;
913 for (int f = n; f > 0; --f, ++sp) *sp = ((uc >> 6*(f-1)) & (B5|B4|B3|B2|B1|B0)) | B7; /* 10xxxxxx */
914 return n+1;
915 invalid:
916 /* U+FFFD */
917 *s++ = '\xEF';
918 *s++ = '\xBF';
919 *s = '\xBD';
920 return 3;
924 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
925 UTF-8 otherwise return 0 */
926 static int isfullutf8 (const char *s, int b) {
927 uchar *c1, *c2, *c3;
929 c1 = (uchar *) s;
930 c2 = (uchar *) ++s;
931 c3 = (uchar *) ++s;
932 if (b < 1) return 0;
933 if ((*c1&(B7|B6|B5)) == (B7|B6) && b == 1) return 0;
934 if ((*c1&(B7|B6|B5|B4)) == (B7|B6|B5) && (b == 1 || (b == 2 && (*c2&(B7|B6)) == B7))) return 0;
935 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;
936 return 1;
940 static int utf8size (const char *s) {
941 uchar c = *s;
943 if (~c&B7) return 1;
944 if ((c&(B7|B6|B5)) == (B7|B6)) return 2;
945 if ((c&(B7|B6|B5|B4)) == (B7|B6|B5)) return 3;
946 return 4;
950 static int utf8strlen (const char *s) {
951 int len = 0;
953 while (*s) {
954 if (((unsigned char)(s[0])&0xc0) == 0xc0 || ((unsigned char)(s[0])&0x80) == 0) ++len;
955 ++s;
957 return len;
961 static void utf8choplast (char *s) {
962 int lastpos = 0;
964 for (char *t = s; *t; ++t) {
965 if (((unsigned char)(t[0])&0xc0) == 0xc0 || ((unsigned char)(t[0])&0x80) == 0) lastpos = (int)(t-s);
967 s[lastpos] = 0;
971 ////////////////////////////////////////////////////////////////////////////////
972 // utilities
973 static char *SPrintfVA (const char *fmt, va_list vaorig) {
974 char *buf = NULL;
975 int olen, len = 128;
977 buf = malloc(len);
978 if (buf == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
979 for (;;) {
980 char *nb;
981 va_list va;
983 va_copy(va, vaorig);
984 olen = vsnprintf(buf, len, fmt, va);
985 va_end(va);
986 if (olen >= 0 && olen < len) return buf;
987 if (olen < 0) olen = len*2-1;
988 nb = realloc(buf, olen+1);
989 if (nb == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
990 buf = nb;
991 len = olen+1;
996 static __attribute__((format(printf,1,2))) char *SPrintf (const char *fmt, ...) {
997 char *buf = NULL;
998 va_list va;
1000 va_start(va, fmt);
1001 buf = SPrintfVA(fmt, va);
1002 va_end(va);
1003 return buf;
1007 static __attribute__((noreturn)) __attribute__((format(printf,1,2))) void die (const char *errstr, ...) {
1008 va_list ap;
1010 fprintf(stderr, "FATAL: ");
1011 va_start(ap, errstr);
1012 vfprintf(stderr, errstr, ap);
1013 va_end(ap);
1014 fprintf(stderr, "\n");
1015 exit(EXIT_FAILURE);
1019 ////////////////////////////////////////////////////////////////////////////////
1020 // getticks
1021 static struct timespec mclk_sttime; // starting time of monotonic clock
1024 static void mclock_init (void) {
1025 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &mclk_sttime);
1029 static uint mclock_ticks (void) {
1030 struct timespec tp;
1032 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &tp);
1033 tp.tv_sec -= mclk_sttime.tv_sec;
1034 return tp.tv_sec*1000+(tp.tv_nsec/1000000);
1038 ////////////////////////////////////////////////////////////////////////////////
1039 // locale conversions
1040 static iconv_t icFromLoc;
1041 static iconv_t icToLoc;
1042 static int needConversion = 0;
1043 static const char *cliLocale = NULL;
1046 static void initLCConversion (void) {
1047 const char *lct = setlocale(LC_CTYPE, NULL);
1048 char *cstr;
1050 needConversion = 0;
1051 if (cliLocale == NULL) {
1052 if (strrchr(lct, '.') != NULL) lct = strrchr(lct, '.')+1;
1053 } else {
1054 lct = cliLocale;
1056 if (strcasecmp(lct, "utf8") == 0 || strcasecmp(lct, "utf-8") == 0) return;
1057 //fprintf(stderr, "locale: [%s]\n", lct);
1058 icFromLoc = iconv_open("UTF-8", lct);
1059 if (icFromLoc == (iconv_t)-1) die("can't initialize locale conversion");
1060 cstr = SPrintf("%s//TRANSLIT", lct);
1061 icToLoc = iconv_open(cstr, "UTF-8");
1062 free(cstr);
1063 if (icToLoc == (iconv_t)-1) die("can't initialize locale conversion");
1064 needConversion = 1;
1068 static int loc2utf (char *dest, const char *src, int len) {
1069 if (needConversion) {
1070 char *ibuf, *obuf;
1071 size_t il, ol, ool;
1073 ibuf = (char *)src;
1074 obuf = dest;
1075 il = len;
1076 ool = ol = il*4;
1077 il = iconv(icFromLoc, &ibuf, &il, &obuf, &ol);
1078 if (il == (size_t)-1) return 0;
1079 return ool-ol;
1080 } else {
1081 if (len > 0) memmove(dest, src, len);
1082 return len;
1087 static int utf2loc (char *dest, const char *src, int len) {
1088 if (needConversion) {
1089 char *ibuf, *obuf;
1090 size_t il, ol, ool;
1092 ibuf = (char *)src;
1093 obuf = dest;
1094 il = len;
1095 ool = ol = il*4;
1096 il = iconv(icToLoc, &ibuf, &il, &obuf, &ol);
1097 if (il == (size_t)-1) return 0;
1098 return ool-ol;
1099 } else {
1100 if (len > 0) memmove(dest, src, len);
1101 return len;
1106 ////////////////////////////////////////////////////////////////////////////////
1107 static void fixWindowTitle (const Term *t) {
1108 const char *title = (t != NULL) ? t->title : NULL;
1110 if (title == NULL || !title[0]) {
1111 title = opt_title;
1112 if (title == NULL) title = "";
1114 XStoreName(xw.dpy, xw.win, title);
1115 XChangeProperty(xw.dpy, xw.win, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, (const unsigned char *)title, strlen(title));
1119 // find latest active terminal (but not current %-)
1120 static int findTermToSwitch (void) {
1121 int maxlat = -1, idx = -1;
1123 for (int f = 0; f < term_count; ++f) {
1124 if (term != term_array[f] && term_array[f]->lastActiveTime > maxlat) {
1125 maxlat = term_array[f]->lastActiveTime;
1126 idx = f;
1129 if (idx < 0) {
1130 if (termidx == 0) idx = 0; else idx = termidx+1;
1131 if (idx > term_count) idx = term_count-1;
1133 return idx;
1137 static void fixFirstTab (void) {
1138 if (termidx < firstVisibleTab) firstVisibleTab = termidx;
1139 else if (termidx > firstVisibleTab+opt_tabcount-1) firstVisibleTab = termidx-opt_tabcount+1;
1140 if (firstVisibleTab < 0) firstVisibleTab = 0;
1141 updateTabBar = 1;
1145 static void switchToTerm (int idx, int redraw) {
1146 if (idx >= 0 && idx < term_count && term_array[idx] != NULL && term_array[idx] != term) {
1147 int tt = mclock_ticks();;
1149 if (term != NULL) term->lastActiveTime = tt;
1150 termidx = idx;
1151 term = term_array[termidx];
1152 term->curbhidden = 0;
1153 term->lastBlinkTime = tt;
1155 fixFirstTab();
1157 xseturgency(0);
1158 tfulldirt();
1159 fixWindowTitle(term);
1160 updateTabBar = 1;
1161 if (redraw) draw(1);
1162 //FIXME: optimize memory allocations
1163 if (term->sel.clip != NULL && term->sel.bx >= 0) {
1164 if (lastSelStr != NULL) free(lastSelStr);
1165 lastSelStr = strdup(term->sel.clip);
1166 //fprintf(stderr, "newsel: [%s]\n", lastSelStr);
1168 xfixsel();
1169 //fprintf(stderr, "term #%d\n", termidx);
1170 //fprintf(stderr, "needConv: %d\n", term->needConv);
1175 static Term *termalloc (void) {
1176 Term *t;
1178 if (term_count >= term_array_size) {
1179 int newsz = (term_count==0) ? 1 : term_array_size+64;
1180 Term **n = realloc(term_array, sizeof(Term *)*newsz);
1182 if (n == NULL && term_count == 0) die("out of memory!");
1183 term_array = n;
1184 term_array_size = newsz;
1186 if ((t = calloc(1, sizeof(Term))) == NULL) return NULL;
1187 t->wrbufsize = WBUFSIZ;
1188 t->deffg = defaultFG;
1189 t->defbg = defaultBG;
1190 t->dead = 1;
1191 t->needConv = (needConversion ? 1 : 0);
1192 t->belltype = (opt_audiblebell?BELL_AUDIO:0)|(opt_urgentbell?BELL_URGENT:0);
1193 t->curblink = opt_cursorBlink;
1194 t->curblinkinactive = opt_cursorBlinkInactive;
1195 term_array[term_count++] = t;
1196 return t;
1200 // newer delete last terminal!
1201 static void termfree (int idx) {
1202 if (idx >= 0 && idx < term_count && term_array[idx] != NULL) {
1203 Term *t = term_array[idx];
1205 if (t->pid != 0) {
1206 kill(t->pid, SIGKILL);
1207 return;
1209 if (t->cmdfd >= 0) {
1210 close(t->cmdfd);
1211 t->cmdfd = -1;
1213 exitcode = t->exitcode;
1214 if (idx == termidx) {
1215 if (term_count > 1) {
1216 t->dead = 1;
1217 //switchToTerm((idx > 0) ? idx-1 : 1, 0);
1218 switchToTerm(findTermToSwitch(), 0);
1219 return;
1221 term = NULL;
1223 for (int y = 0; y < t->row; ++y) free(t->alt[y]);
1224 for (int y = 0; y < t->linecount; ++y) {
1225 //fprintf(stderr, "y=%d\n", y);
1226 free(t->line[y]);
1228 free(t->dirty);
1229 free(t->alt);
1230 free(t->line);
1231 if (t->execcmd != NULL) free(t->execcmd);
1232 // condense array
1233 if (termidx > idx) {
1234 // not current, and current at the right
1235 --termidx;
1237 for (int f = idx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
1238 --term_count;
1239 XFreePixmap(xw.dpy, t->picbuf);
1240 free(t);
1245 static void termcleanup (void) {
1246 int f = 0, needredraw = 0;
1248 while (f < term_count) {
1249 if (term_array[f]->dead) {
1250 termfree(f);
1251 needredraw = 1;
1252 } else {
1253 ++f;
1256 if (needredraw) {
1257 updateTabBar = 1;
1258 draw(1);
1263 //FIXME: is it safe to assume that signal interrupted main program?
1264 static void sigchld (int a) {
1265 //if (waitpid(term->pid, &stat, 0) < 0) die("waiting for pid %hd failed: %s", term->pid, SERRNO);
1266 for (;;) {
1267 int stat = 0;
1268 pid_t res = waitpid(-1, &stat, WNOHANG);
1270 if (res == (pid_t)-1 || res == 0) break;
1272 for (int f = 0; f < term_count; ++f) {
1273 if (term_array[f]->pid == res) {
1274 // this terminal should die
1275 //if (term_count == 1) exit(0);
1276 //close(term_array[f]->cmdfd);
1277 //term_array[f]->cmdfd = -1;
1278 term_array[f]->dead = 1;
1279 term_array[f]->pid = 0;
1280 term_array[f]->exitcode = (WIFEXITED(stat)) ? WEXITSTATUS(stat) : 127;
1281 //fprintf(stderr, "exitcode=%d\n", term_array[f]->exitcode);
1282 updateTabBar = 1;
1283 break;
1287 signal(SIGCHLD, sigchld);
1291 ////////////////////////////////////////////////////////////////////////////////
1292 static void keytrans_reset (void) {
1293 if (keytrans) free(keytrans);
1294 keytrans = NULL;
1295 keytrans_size = 0;
1296 keytrans_used = 0;
1300 static void keytrans_add (const char *src, const char *dst) {
1301 KeySym kssrc = XStringToKeysym(src);
1302 KeySym ksdst = XStringToKeysym(dst);
1304 if (kssrc == NoSymbol) die("invalid keysym: '%s'", src);
1305 if (ksdst == NoSymbol) die("invalid keysym: '%s'", dst);
1306 if (kssrc == ksdst) return; // idiot
1308 for (int f = 0; f < keytrans_used; ++f) {
1309 if (keytrans[f].src == kssrc) {
1310 // replace
1311 keytrans[f].dst = ksdst;
1312 return;
1316 if (keytrans_used >= keytrans_size) {
1317 int newsize = keytrans_size+64;
1318 KeyTransDef *n = realloc(keytrans, sizeof(KeyTransDef)*newsize);
1320 if (n == NULL) die("out of memory");
1321 keytrans_size = newsize;
1322 keytrans = n;
1324 keytrans[keytrans_used].src = kssrc;
1325 keytrans[keytrans_used].dst = ksdst;
1326 ++keytrans_used;
1330 ////////////////////////////////////////////////////////////////////////////////
1331 static void parsekeyname (const char *str, KeySym *ks, uint *mask, int *kp) {
1332 char *s = alloca(strlen(str)+1);
1334 if (s == NULL) die("out of memory");
1335 strcpy(s, str);
1336 *kp = 0;
1337 *ks = NoSymbol;
1338 *mask = XK_NO_MOD;
1340 while (*s) {
1341 char *e, oc;
1342 uint mm = 0;
1343 int mod = 1;
1345 while (*s && isspace(*s)) ++s;
1346 for (e = s; *e && !isspace(*e) && *e != '+'; ++e) ;
1347 oc = *e; *e = 0;
1349 if (strcasecmp(s, "alt") == 0) mm = Mod1Mask;
1350 else if (strcasecmp(s, "win") == 0) mm = Mod4Mask;
1351 else if (strcasecmp(s, "ctrl") == 0) mm = ControlMask;
1352 else if (strcasecmp(s, "shift") == 0) mm = ShiftMask;
1353 else if (strcasecmp(s, "any") == 0) mm = XK_NO_MOD; //!
1354 else if (strcasecmp(s, "kpad") == 0) *kp = 1;
1355 else {
1356 mod = 0;
1357 if ((*ks = XStringToKeysym(s)) == NoSymbol) break;
1358 //fprintf(stderr, "[%s]\n", s);
1361 *e = oc;
1362 s = e;
1363 while (*s && isspace(*s)) ++s;
1364 if (mod) {
1365 if (*s != '+') { *ks = NoSymbol; break; }
1366 ++s;
1367 if (mm != 0) {
1368 if (mm == XK_NO_MOD) *mask = XK_ANY_MOD;
1369 else if (*mask == XK_NO_MOD) *mask = mm;
1370 else if (*mask != XK_ANY_MOD) *mask |= mm;
1372 } else {
1373 if (*s) { *ks = NoSymbol; break; }
1376 if (*ks == NoSymbol) die("invalid key name: '%s'", str);
1377 //fprintf(stderr, "mask=0x%08x, kp=%d\n", *mask, *kp);
1381 ////////////////////////////////////////////////////////////////////////////////
1382 static void keybinds_reset (void) {
1383 if (keybinds) free(keybinds);
1384 keybinds = NULL;
1385 keybinds_size = 0;
1386 keybinds_used = 0;
1390 static void keybind_add (const char *key, const char *act) {
1391 KeySym ks;
1392 uint mask;
1393 int kp;
1395 parsekeyname(key, &ks, &mask, &kp);
1397 for (int f = 0; f < keybinds_used; ++f) {
1398 if (keybinds[f].key == ks && keybinds[f].mask == mask) {
1399 // replace or remove
1400 free(keybinds[f].str);
1401 if (act == NULL || !act[0]) {
1402 // remove
1403 for (int c = f+1; c < keybinds_used; ++c) keybinds[c-1] = keybinds[c];
1404 } else {
1405 // replace
1406 if ((keybinds[f].str = strdup(act)) == NULL) die("out of memory");
1408 return;
1412 if (keybinds_used >= keybinds_size) {
1413 int newsize = keybinds_size+64;
1414 KeyInfoDef *n = realloc(keybinds, sizeof(KeyInfoDef)*newsize);
1416 if (n == NULL) die("out of memory");
1417 keybinds_size = newsize;
1418 keybinds = n;
1420 keybinds[keybinds_used].key = ks;
1421 keybinds[keybinds_used].mask = mask;
1422 keybinds[keybinds_used].kp = 0;
1423 if ((keybinds[keybinds_used].str = strdup(act)) == NULL) die("out of memory");
1424 ++keybinds_used;
1428 ////////////////////////////////////////////////////////////////////////////////
1429 static void keymap_reset (void) {
1430 if (keymap) free(keymap);
1431 keymap = NULL;
1432 keymap_size = 0;
1433 keymap_used = 0;
1437 static void keymap_add (const char *key, const char *act) {
1438 KeySym ks;
1439 uint mask;
1440 int kp;
1442 parsekeyname(key, &ks, &mask, &kp);
1444 for (int f = 0; f < keymap_used; ++f) {
1445 if (keymap[f].key == ks && keymap[f].mask == mask && keymap[f].kp == kp) {
1446 // replace or remove
1447 free(keymap[f].str);
1448 if (act == NULL) {
1449 // remove
1450 for (int c = f+1; c < keymap_used; ++c) keymap[c-1] = keymap[c];
1451 } else {
1452 // replace
1453 if ((keymap[f].str = strdup(act)) == NULL) die("out of memory");
1455 return;
1459 if (keymap_used >= keymap_size) {
1460 int newsize = keymap_size+128;
1461 KeyInfoDef *n = realloc(keymap, sizeof(KeyInfoDef)*newsize);
1463 if (n == NULL) die("out of memory");
1464 keymap_size = newsize;
1465 keymap = n;
1467 keymap[keymap_used].key = ks;
1468 keymap[keymap_used].mask = mask;
1469 keymap[keymap_used].kp = kp;
1470 if ((keymap[keymap_used].str = strdup(act)) == NULL) die("out of memory");
1471 ++keymap_used;
1475 ////////////////////////////////////////////////////////////////////////////////
1476 // selection
1477 static inline void setWantRedraw (void) {
1478 if (term != NULL) {
1479 term->wantRedraw = 1;
1480 term->lastDrawTime = mclock_ticks(); //FIXME: avoid excess redraw?
1485 static void inline markDirty (int lineno, int flag) {
1486 if (term != NULL && lineno >= 0 && lineno < term->row) {
1487 term->dirty[lineno] |= flag;
1488 term->wantRedraw = 1;
1489 term->lastDrawTime = mclock_ticks(); //FIXME: avoid excess redraw?
1494 static void selinit (void) {
1495 term->sel.tclick1 = term->sel.tclick2 = mclock_ticks();
1496 term->sel.mode = 0;
1497 term->sel.bx = -1;
1498 term->sel.clip = NULL;
1499 term->sel.xtarget = XA_UTF8;
1500 //if (term->sel.xtarget == None) term->sel.xtarget = XA_STRING;
1504 static void selhide (void) {
1505 if (term->sel.bx != -1) {
1506 term->sel.mode = 0;
1507 term->sel.bx = -1;
1508 tfulldirt();
1513 static inline int selected (int x, int y) {
1514 if (term->sel.bx == -1) return 0;
1516 if (y >= term->row) y = 0-(y-term->row+1);
1517 if (term->sel.ey == y && term->sel.by == y) {
1518 int bx = MIN(term->sel.bx, term->sel.ex);
1519 int ex = MAX(term->sel.bx, term->sel.ex);
1521 return BETWEEN(x, bx, ex);
1524 return
1525 ((term->sel.b.y < y && y < term->sel.e.y) || (y == term->sel.e.y && x <= term->sel.e.x)) ||
1526 (y == term->sel.b.y && x >= term->sel.b.x && (x <= term->sel.e.x || term->sel.b.y != term->sel.e.y));
1530 static void getbuttoninfo (XEvent *e, int *b, int *x, int *y) {
1531 if (b != NULL) *b = e->xbutton.button;
1532 if (x != NULL) *x = X2COL(e->xbutton.x);
1533 if (y != NULL) *y = Y2ROW(e->xbutton.y);
1534 term->sel.b.x = (term->sel.by < term->sel.ey ? term->sel.bx : term->sel.ex);
1535 term->sel.b.y = MIN(term->sel.by, term->sel.ey);
1536 term->sel.e.x = (term->sel.by < term->sel.ey ? term->sel.ex : term->sel.bx);
1537 term->sel.e.y = MAX(term->sel.by, term->sel.ey);
1541 static void mousereport (XEvent *e) {
1542 int x = X2COL(e->xbutton.x);
1543 int y = Y2ROW(e->xbutton.y);
1544 int button = e->xbutton.button;
1545 int state = e->xbutton.state;
1546 //char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
1547 char buf[32], *p;
1548 char lastCh = 'M';
1549 int ss;
1551 if (term == NULL) return;
1552 if (x < 0 || x >= term->col || y < 0 || y >= term->row) return;
1554 #if 0
1555 case 1000: /* X11 xterm mouse reporting */
1556 case 1005: /* utf-8 mouse encoding */
1557 case 1006: /* sgr mouse encoding */
1558 case 1015: /* urxvt mouse encoding */
1559 #endif
1560 //sprintf(buf, "\x1b[M%c%c%c", 0, 32+x+1, 32+y+1);
1561 p = buf+sprintf(buf, "\x1b[M");
1562 /* from urxvt */
1563 if (e->xbutton.type == MotionNotify) {
1564 if (!IS_SET(MODE_MOUSEMOTION) || (x == term->mouseox && y == term->mouseoy)) return;
1565 button = term->mouseob+32;
1566 term->mouseox = x;
1567 term->mouseoy = y;
1568 } else if (e->xbutton.type == ButtonRelease || button == AnyButton) {
1569 if (term->mousemode != 1006) {
1570 button = 3; // 'release' flag
1571 } else {
1572 lastCh = 'm';
1573 button -= Button1;
1574 if (button >= 3) button += 64-3;
1576 } else {
1577 button -= Button1;
1578 if (button >= 3) button += 64-3;
1579 if (e->xbutton.type == ButtonPress) {
1580 term->mouseob = button;
1581 term->mouseox = x;
1582 term->mouseoy = y;
1585 ss = (state & ShiftMask ? 4 : 0)+(state & Mod4Mask ? 8 : 0)+(state & ControlMask ? 16 : 0);
1586 switch (term->mousemode) {
1587 case 1006: /* sgr */
1588 buf[2] = '<';
1589 p += sprintf(p, "%d;", button+ss);
1590 break;
1591 case 1015: /* urxvt */
1592 --p; // remove 'M'
1593 p += sprintf(p, "%d;", 32+button+ss);
1594 break;
1595 default:
1596 *p++ = 32+button+ss;
1597 break;
1599 // coords
1600 switch (term->mousemode) {
1601 case 1005: /* utf-8 */
1602 p += utf8encode(x+1, p);
1603 p += utf8encode(y+1, p);
1604 break;
1605 case 1006: /* sgr */
1606 p += sprintf(p, "%d;%d%c", x+1, y+1, lastCh);
1607 break;
1608 case 1015: /* urxvt */
1609 p += sprintf(p, "%d;%dM", x+1, y+1);
1610 break;
1611 default:
1612 p += sprintf(p, "%c%c", 32+x+1, 32+y+1);
1613 break;
1615 *p = 0;
1618 fprintf(stderr, "(%d)<", term->mousemode);
1619 for (const unsigned char *s = (const unsigned char *)buf; *s; ++s) {
1620 if (s[0] < 32) fprintf(stderr, "{%d}", s[0]); else fputc(s[0], stderr);
1622 fputs(">\n", stderr);
1625 ttywritestr(buf);
1629 static void xfixsel (void) {
1630 if (lastSelStr != NULL) {
1631 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
1632 XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, xw.win, CurrentTime);
1633 } else {
1634 if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) == xw.win) XSetSelectionOwner(xw.dpy, XA_PRIMARY, None, CurrentTime);
1635 if (XGetSelectionOwner(xw.dpy, XA_CLIPBOARD) == xw.win) XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, None, CurrentTime);
1637 XFlush(xw.dpy);
1641 static void xsetsel (char *str) {
1642 /* register the selection for both the clipboard and the primary */
1643 if (term == NULL) return;
1644 if (term->sel.clip != NULL) free(term->sel.clip);
1645 term->sel.clip = str;
1646 if (lastSelStr != NULL) free(lastSelStr);
1647 lastSelStr = (str != NULL ? strdup(str) : NULL);
1648 xfixsel();
1649 //fprintf(stderr, "[%s]\n", str);
1653 static void selclear (XEvent *e) {
1654 if (lastSelStr != NULL) free(lastSelStr);
1655 lastSelStr = NULL;
1656 if (term != NULL) {
1657 if (term->sel.clip != NULL) free(term->sel.clip);
1658 term->sel.clip = NULL;
1659 term->sel.mode = 0;
1660 if (term->sel.bx != 0) {
1661 term->sel.bx = -1;
1662 tfulldirt();
1663 draw(1);
1669 static Line selgetlinebyy (int y) {
1670 Line l;
1672 if (y >= term->row) return NULL;
1673 if (y < 0) {
1674 if (y < -(term->maxhistory)) return NULL;
1675 l = term->line[term->row+(-y)-1];
1676 } else {
1677 l = term->line[y];
1679 return l;
1683 static void selcopy (void) {
1684 char *str, *ptr;
1685 int x, y, bufsize, is_selected = 0;
1687 if (term == NULL || term->sel.bx == -1) {
1688 str = NULL;
1689 } else {
1690 int sy = MIN(term->sel.e.y, term->sel.b.y);
1691 int ey = MAX(term->sel.e.y, term->sel.b.y);
1694 fprintf(stderr, "bx=%d; ex=%d; by=%d; ey=%d\n", term->sel.bx, term->sel.by, term->sel.ex, term->sel.ey);
1695 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);
1696 fprintf(stderr, " sy=%d; ey=%d\n", sy, ey);
1698 if (ey >= term->row) { selclear(NULL); return; }
1699 bufsize = (term->col+1)*(ey-sy+1)*UTF_SIZ;
1700 ptr = str = malloc(bufsize);
1701 /* append every set & selected glyph to the selection */
1702 for (y = sy; y <= ey; ++y) {
1703 Line l = selgetlinebyy(y);
1704 char *pstart = ptr;
1706 if (l == NULL) continue;
1707 for (x = 0; x < term->col; ++x) {
1708 if ((is_selected = selected(x, y)) != 0) {
1709 int size = utf8size(l[x].c);
1711 //if (size == 1) fprintf(stderr, "x=%d; y=%d; size=%d; c=%d\n", x, y, size, l[x].c[0]);
1712 if (size == 1) {
1713 unsigned char c = (unsigned char)l[x].c[0];
1715 *ptr = (c < 32 || c >= 127) ? ' ' : c;
1716 } else {
1717 memcpy(ptr, l[x].c, size);
1719 ptr += size;
1722 //fprintf(stderr, "y=%d; linebytes=%d\n", y, (int)(ptr-pstart));
1723 // trim trailing spaces
1724 while (ptr > pstart && ptr[-1] == ' ') --ptr;
1725 // \n at the end of every unwrapped selected line except for the last one
1726 if (is_selected && y < ey && selected(term->col-1, y) && !(l[term->col-1].state&GLYPH_WRAP)) *ptr++ = '\n';
1728 *ptr = 0;
1730 xsetsel(str);
1731 if (!str || !str[0]) selclear(NULL);
1735 static void selnotify (XEvent *e) {
1736 ulong nitems, ofs, rem;
1737 int format;
1738 uchar *data;
1739 Atom type;
1740 XSelectionEvent *se = (XSelectionEvent *)e;
1741 int isutf8;
1742 int wasbrk = 0;
1743 char *ucbuf = NULL;
1744 int ucbufsize = 0;
1746 if (term == NULL || term->cmdMode == CMDMODE_MESSAGE) return;
1747 #ifdef PASTE_SELECTION_DEBUG
1749 char *name;
1751 fprintf(stderr, "selnotify!\n");
1753 name = se->selection != None ? XGetAtomName(se->display, se->selection) : NULL;
1754 fprintf(stderr, " selection: [%s]\n", name);
1755 if (name != NULL) XFree(name);
1757 name = se->target != None ? XGetAtomName(se->display, se->target) : NULL;
1758 fprintf(stderr, " target: [%s]\n", name);
1759 if (name != NULL) XFree(name);
1761 name = se->property != None ? XGetAtomName(se->display, se->property) : NULL;
1762 fprintf(stderr, " property: [%s]\n", name);
1763 if (name != NULL) XFree(name);
1765 #endif
1767 if (se->property != XA_VT_SELECTION) return;
1769 #ifdef PASTE_SELECTION_DEBUG
1771 fprintf(stderr, "selection:\n");
1772 fprintf(stderr, " primary: %d\n", se->selection == XA_PRIMARY);
1773 fprintf(stderr, " secondary: %d\n", se->selection == XA_SECONDARY);
1774 fprintf(stderr, " clipboard: %d\n", se->selection == XA_CLIPBOARD);
1775 fprintf(stderr, " vtsel: %d\n", se->selection == XA_VT_SELECTION);
1776 fprintf(stderr, "target:\n");
1777 fprintf(stderr, " primary: %d\n", se->target == XA_PRIMARY);
1778 fprintf(stderr, " secondary: %d\n", se->target == XA_SECONDARY);
1779 fprintf(stderr, " clipboard: %d\n", se->target == XA_CLIPBOARD);
1780 fprintf(stderr, " vtsel: %d\n", se->target == XA_VT_SELECTION);
1782 #endif
1783 if (se->target == XA_UTF8) {
1784 isutf8 = 1;
1785 } else if (se->target == XA_STRING) {
1786 isutf8 = 0;
1787 } else if (se->target == XA_TARGETS) {
1788 Atom rqtype = None, *targ;
1790 if (XGetWindowProperty(xw.dpy, xw.win, se->property, 0, 65536, False, XA_ATOM, &type, &format, &nitems, &rem, &data)) {
1791 //fprintf(stderr, "no targets\n");
1792 rqtype = XA_STRING;
1793 } else {
1794 for (targ = (Atom *)data; nitems > 0; --nitems, ++targ) {
1795 #ifdef PASTE_SELECTION_DEBUG
1796 fprintf(stderr, " TGT: [%s]\n", XGetAtomName(se->display, *targ));
1797 #endif
1798 if (*targ == XA_UTF8) rqtype = XA_UTF8;
1799 else if (*targ == XA_STRING && rqtype == None) rqtype = XA_STRING;
1801 XFree(data);
1803 if (rqtype != None) XConvertSelection(xw.dpy, se->selection, rqtype, XA_VT_SELECTION, xw.win, CurrentTime);
1804 return;
1805 } else {
1806 return;
1809 ofs = 0;
1810 do {
1811 int blen;
1812 char *str;
1814 if (XGetWindowProperty(xw.dpy, xw.win, se->property, ofs, BUFSIZ/4, False, AnyPropertyType, &type, &format, &nitems, &rem, &data)) {
1815 fprintf(stderr, "Clipboard allocation failed\n");
1816 break;
1818 //fprintf(stderr, "nitems=%d; format=%d; rem=%d\n", (int)nitems, format, (int)rem);
1819 blen = nitems*format/8;
1821 if (!isutf8) {
1822 int newsz = blen*4+64;
1824 if (ucbufsize < newsz) {
1825 char *n = realloc(ucbuf, newsz);
1827 if (n == NULL) { XFree(data); break; }
1828 ucbuf = n;
1829 ucbufsize = newsz;
1832 blen = loc2utf(ucbuf, (const char *)data, blen);
1833 str = ucbuf;
1834 } else {
1835 str = (char *)data;
1838 if (term->cmdMode != CMDMODE_NONE) {
1839 tcmdput(str, blen);
1840 } else {
1841 if (nitems*format/8 > 0 && !wasbrk && IS_SET(MODE_BRACPASTE)) {
1842 wasbrk = 1;
1843 ttywritestr("\x1b[200~");
1845 ttywrite(str, blen);
1847 XFree(data);
1848 /* number of 32-bit chunks returned */
1849 ofs += nitems*format/32;
1850 } while (rem > 0);
1852 if (wasbrk) ttywritestr("\x1b[201~");
1853 if (ucbuf != NULL) free(ucbuf);
1857 static void selpaste (Atom which) {
1858 if (term == NULL) return;
1859 if (XGetSelectionOwner(xw.dpy, which) == None) return;
1860 //XConvertSelection(xw.dpy, which, term->sel.xtarget, XA_VT_SELECTION, xw.win, CurrentTime);
1861 XConvertSelection(xw.dpy, which, XA_TARGETS, XA_VT_SELECTION, xw.win, CurrentTime);
1863 if (which == XA_PRIMARY) selpaste(XA_SECONDARY);
1864 else if (which == XA_SECONDARY) selpaste(XA_CLIPBOARD);
1869 static void selrequest (XEvent *e) {
1870 XSelectionRequestEvent *xsre;
1871 XSelectionEvent xev;
1873 if (lastSelStr == NULL) return;
1874 xsre = (XSelectionRequestEvent *)e;
1875 xev.type = SelectionNotify;
1876 xev.requestor = xsre->requestor;
1877 xev.selection = xsre->selection;
1878 xev.target = xsre->target;
1879 xev.time = xsre->time;
1880 /* reject */
1881 xev.property = None;
1882 if (xsre->target == XA_TARGETS) {
1883 /* respond with the supported type */
1884 Atom tlist[3] = {XA_UTF8, XA_STRING, XA_TARGETS};
1886 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_ATOM, 32, PropModeReplace, (uchar *)tlist, 3);
1887 xev.property = xsre->property;
1888 } else if (xsre->target == XA_UTF8 && lastSelStr != NULL) {
1889 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_UTF8, 8, PropModeReplace, (uchar *)lastSelStr, strlen(lastSelStr));
1890 xev.property = xsre->property;
1891 } else if (xsre->target == XA_STRING && lastSelStr != NULL) {
1892 char *s = malloc(strlen(lastSelStr)*4+8);
1894 if (s != NULL) {
1895 int len = utf2loc(s, lastSelStr, strlen(lastSelStr));
1897 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_STRING, 8, PropModeReplace, (uchar *)s, len);
1898 xev.property = xsre->property;
1899 free(s);
1902 /* all done, send a notification to the listener */
1903 if (!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *)&xev)) fprintf(stderr, "Error sending SelectionNotify event\n");
1907 static int x2tab (int x) {
1908 if (x >= 0 && x < xw.w && xw.tabheight > 0) {
1909 x /= (xw.w/opt_tabcount)+firstVisibleTab;
1910 return (x >= 0 && x < term_count) ? x : -1;
1912 return -1;
1916 static void msTabSwitch (XEvent *e) {
1917 int tabn = x2tab(e->xbutton.x)+firstVisibleTab;
1919 if (tabn >= 0 && tabn != termidx) switchToTerm(tabn, 1);
1923 static void msTabScrollLeft (void) {
1924 if (firstVisibleTab > 0) {
1925 --firstVisibleTab;
1926 updateTabBar = 1;
1931 static void msTabScrollRight (void) {
1932 int newidx = firstVisibleTab+1;
1934 if (newidx > term_count-opt_tabcount) return;
1935 firstVisibleTab = newidx;
1936 updateTabBar = 1;
1940 static void bpress (XEvent *e) {
1941 if (term == NULL) return;
1943 if (xw.tabheight > 0) {
1944 if ((opt_tabposition == 0 && e->xbutton.y >= xw.h-xw.tabheight) ||
1945 (opt_tabposition != 0 && e->xbutton.y < xw.tabheight)) {
1946 switch (e->xbutton.button) {
1947 case Button1: // left
1948 msTabSwitch(e);
1949 break;
1950 case Button4: // wheel up
1951 msTabScrollLeft();
1952 break;
1953 case Button5: // wheel down
1954 msTabScrollRight();
1955 break;
1957 return;
1961 if ((e->xbutton.state&ShiftMask) != 0) {
1962 if (e->xbutton.button == Button1) {
1963 if (term->sel.bx != -1) tsetdirt(term->sel.b.y, term->sel.e.y);
1964 term->sel.mode = 1;
1965 term->sel.ex = term->sel.bx = X2COL(e->xbutton.x);
1966 term->sel.ey = term->sel.by = Y2ROW(e->xbutton.y);
1967 //fprintf(stderr, "x=%d; y=%d\n", term->sel.bx, term->sel.by);
1968 draw(1);
1969 return;
1972 if (e->xbutton.button == Button3) {
1973 term->sel.bx = -1;
1974 selcopy();
1975 draw(1);
1978 return;
1980 if (IS_SET(MODE_MOUSE)) mousereport(e);
1984 static void brelease (XEvent *e) {
1985 if (term == NULL) return;
1987 switch (opt_tabposition) {
1988 case 0: // bottom
1989 if (e->xbutton.y >= xw.h-xw.tabheight) return;
1990 break;
1991 case 1: // top
1992 if (e->xbutton.y < xw.tabheight) return;
1993 break;
1996 if ((e->xbutton.state&ShiftMask) == 0 && !term->sel.mode) {
1997 if (IS_SET(MODE_MOUSE)) mousereport(e);
1998 return;
2001 if (e->xbutton.button == Button2) {
2002 selpaste(XA_PRIMARY);
2003 } else if (e->xbutton.button == Button1) {
2004 term->sel.mode = 0;
2005 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey); // this sets sel.b and sel.e
2007 if (term->sel.bx == term->sel.ex && term->sel.by == term->sel.ey) {
2008 // single line, single char selection
2009 int now;
2011 markDirty(term->sel.ey, 2);
2012 term->sel.bx = -1;
2013 now = mclock_ticks();
2014 if (now-term->sel.tclick2 <= opt_tripleclick_timeout) {
2015 /* triple click on the line */
2016 term->sel.b.x = term->sel.bx = 0;
2017 term->sel.e.x = term->sel.ex = term->col;
2018 term->sel.b.y = term->sel.e.y = term->sel.ey;
2019 } else if (now-term->sel.tclick1 <= opt_doubleclick_timeout) {
2020 /* double click to select word */
2021 Line l = selgetlinebyy(term->sel.ey);
2023 if (l != NULL) {
2024 //FIXME: write better word selection code
2025 term->sel.bx = term->sel.ex;
2026 if (IS_GFX(l[term->sel.bx].attr)) {
2027 while (term->sel.bx > 0 && IS_GFX(l[term->sel.bx-1].attr)) --term->sel.bx;
2028 term->sel.b.x = term->sel.bx;
2029 while (term->sel.ex < term->col-1 && IS_GFX(l[term->sel.ex+1].attr)) ++term->sel.ex;
2030 } else {
2031 while (term->sel.bx > 0 && !IS_GFX(l[term->sel.bx-1].attr) && l[term->sel.bx-1].c[0] != ' ') --term->sel.bx;
2032 term->sel.b.x = term->sel.bx;
2033 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;
2035 term->sel.e.x = term->sel.ex;
2036 term->sel.b.y = term->sel.e.y = term->sel.ey;
2040 selcopy();
2041 draw(1);
2042 } else {
2043 // multiline or multichar selection
2044 selcopy();
2047 term->sel.tclick2 = term->sel.tclick1;
2048 term->sel.tclick1 = mclock_ticks();
2049 //draw(1);
2053 static void bmotion (XEvent *e) {
2054 if (term == NULL) return;
2056 switch (opt_tabposition) {
2057 case 0: // bottom
2058 if (e->xbutton.y >= xw.h-xw.tabheight) return;
2059 break;
2060 case 1: // top
2061 if (e->xbutton.y < xw.tabheight) return;
2062 break;
2065 if (term->sel.mode) {
2066 int oldey = term->sel.ey, oldex = term->sel.ex;
2068 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey); // this sets sel.b and sel.e
2069 if (oldey != term->sel.ey || oldex != term->sel.ex) {
2070 int starty = MIN(oldey, term->sel.ey);
2071 int endy = MAX(oldey, term->sel.ey);
2073 tsetdirt(starty, endy);
2074 draw(1);
2076 return;
2078 //if (IS_SET(MODE_MOUSE) && e->xbutton.button != 0) mousereport(e);
2082 ////////////////////////////////////////////////////////////////////////////////
2083 // tty init
2085 static void dump (char c) {
2086 static int col;
2088 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
2089 if (++col % 10 == 0) fprintf(stderr, "\n");
2094 static __attribute__((noreturn)) void execsh (const char *str) {
2095 char **args;
2097 if (str == NULL) {
2098 char *envshell = getenv("SHELL");
2100 DEFAULT(envshell, opt_shell);
2101 setenv("TERM", opt_term, 1);
2102 args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL};
2103 } else {
2104 int argc = 0;
2106 args = calloc(32768, sizeof(char *));
2107 if (args == NULL) exit(EXIT_FAILURE);
2108 while (*str) {
2109 const char *b;
2111 while (*str && isspace(*str)) ++str;
2112 if (!str[0]) break;
2114 b = str;
2115 while (*str && !isspace(*str)) {
2116 if (*str++ == '\\') {
2117 if (*str) ++str;
2121 args[argc] = calloc(str-b+1, 1);
2122 memcpy(args[argc], b, str-b);
2125 FILE *fo = fopen("z.log", "a");
2126 fprintf(fo, "%d: [%s]\n", argc, args[argc]);
2127 fclose(fo);
2130 ++argc;
2132 if (argc < 1) exit(EXIT_FAILURE);
2134 execvp(args[0], args);
2135 exit(EXIT_FAILURE);
2139 static int ttynew (Term *term) {
2140 int m, s;
2141 struct winsize w = {term->row, term->col, 0, 0};
2142 static int signalset = 0;
2144 if (openpty(&m, &s, NULL, NULL, &w) < 0) die("openpty failed: %s", SERRNO);
2145 term->cmdfd = m;
2146 ttyresize();
2147 term->cmdfd = -1;
2148 switch (term->pid = fork()) {
2149 case -1: /* error */
2150 fprintf(stderr, "fork failed");
2151 return -1;
2152 case 0: /* child */
2153 setsid(); /* create a new process group */
2154 dup2(s, STDIN_FILENO);
2155 dup2(s, STDOUT_FILENO);
2156 dup2(s, STDERR_FILENO);
2157 if (ioctl(s, TIOCSCTTY, NULL) < 0) die("ioctl TIOCSCTTY failed: %s", SERRNO);
2158 close(s);
2159 close(m);
2160 execsh(term->execcmd);
2161 break;
2162 default: /* master */
2163 close(s);
2164 term->cmdfd = m;
2165 term->dead = 0;
2166 ttyresize();
2167 if (!signalset) { signalset = 1; signal(SIGCHLD, sigchld); }
2168 break;
2170 return 0;
2174 ////////////////////////////////////////////////////////////////////////////////
2175 // tty r/w
2176 static int ttycanread (void) {
2177 for (;;) {
2178 fd_set rfd;
2179 struct timeval timeout = {0};
2181 if (term->dead || term->cmdfd < 0) return 0;
2182 FD_ZERO(&rfd);
2183 FD_SET(term->cmdfd, &rfd);
2184 if (select(term->cmdfd+1, &rfd, NULL, NULL, &timeout) < 0) {
2185 if (errno == EINTR) continue;
2186 die("select failed: %s", SERRNO);
2188 if (FD_ISSET(term->cmdfd, &rfd)) return 1;
2189 break;
2191 return 0;
2195 static int ttycanwrite (void) {
2196 for (;;) {
2197 fd_set wfd;
2198 struct timeval timeout = {0};
2200 if (term->dead || term->cmdfd < 0) return 0;
2201 FD_ZERO(&wfd);
2202 FD_SET(term->cmdfd, &wfd);
2203 if (select(term->cmdfd+1, NULL, &wfd, NULL, &timeout) < 0) {
2204 if (errno == EINTR) continue;
2205 die("select failed: %s", SERRNO);
2207 if (FD_ISSET(term->cmdfd, &wfd)) return 1;
2208 break;
2210 return 0;
2214 #ifdef DUMP_IO
2215 static void wrstr (const char *s, int len) {
2216 if (s == NULL) return;
2217 while (len-- > 0) {
2218 unsigned char c = (unsigned char)(*s++);
2220 if (c < 32) fprintf(stderr, "{%u}", c); else fwrite(&c, 1, 1, stderr);
2223 #endif
2226 static void ttyread (void) {
2227 char *ptr;
2228 int left;
2230 /* append read bytes to unprocessed bytes */
2231 if (term == NULL || term->dead || term->cmdfd < 0) return;
2232 #ifdef DUMP_PROG_OUTPUT
2233 term->xobuflen = term->obuflen;
2234 #endif
2235 left = OBUFSIZ-term->obuflen;
2236 //dlogf("0: ttyread before: free=%d, used=%d", left, term->obuflen);
2237 while (left > 0 && ttycanread()) {
2238 int ret;
2240 //if ((ret = read(term->cmdfd, term->obuf+term->obuflen, 1)) < 0) die("Couldn't read from shell: %s", SERRNO);
2241 if ((ret = read(term->cmdfd, term->obuf+term->obuflen, left)) < 0) {
2242 //fprintf(stderr, "Warning: couldn't read from tty #%d: %s\n", termidx, SERRNO);
2243 break;
2245 term->obuflen += ret;
2246 left -= ret;
2248 //dlogf("1: ttyread after: free=%d, used=%d", left, term->obuflen);
2249 /* process every complete utf8 char */
2250 #ifdef DUMP_PROG_OUTPUT
2252 FILE *fo = fopen("zlogo.log", "ab");
2253 if (fo) {
2254 fwrite(term->obuf+term->xobuflen, term->obuflen-term->xobuflen, 1, fo);
2255 fclose(fo);
2258 #endif
2259 ptr = term->obuf;
2260 if (term->needConv) {
2261 // need conversion from locale to utf-8
2262 //fprintf(stderr, "buf: %d bytes\n", term->obuflen);
2263 while (term->obuflen > 0) {
2264 char obuf[UTF_SIZ+1];
2265 int len;
2267 len = loc2utf(obuf, ptr, 1);
2268 #ifdef DUMP_IO_READ
2270 fprintf(stderr, "rdc: [");
2271 wrstr(ptr, 1);
2272 fprintf(stderr, "] --> [");
2273 wrstr(obuf, len);
2274 fprintf(stderr, "]\n");
2275 fflush(stderr);
2277 #endif
2278 if (len > 0) {
2279 obuf[len] = 0;
2280 tputc(obuf);
2282 ++ptr;
2283 --term->obuflen;
2285 term->obuflen = 0;
2286 } else {
2287 // don't do any conversion
2288 while (term->obuflen >= UTF_SIZ || isfullutf8(ptr, term->obuflen)) {
2289 ulong utf8c;
2290 char s[UTF_SIZ+1];
2291 int charsize = utf8decode(ptr, &utf8c); /* returns size of utf8 char in bytes */
2292 int len;
2294 len = utf8encode(utf8c, s);
2295 #ifdef DUMP_IO_READ
2297 fprintf(stderr, "rdx: [");
2298 wrstr(s, len);
2299 fprintf(stderr, "]\n");
2300 fflush(stderr);
2302 #endif
2303 if (len > 0) {
2304 s[len] = 0;
2305 tputc(s);
2307 ptr += charsize;
2308 term->obuflen -= charsize;
2310 //dlogf("2: ttyread afterproc: used=%d", term->obuflen);
2312 /* keep any uncomplete utf8 char for the next call */
2313 if (term->obuflen > 0) memmove(term->obuf, ptr, term->obuflen);
2317 static void ttyflushwrbuf (void) {
2318 if (term == NULL || term->dead || term->cmdfd < 0) return;
2319 if (term->wrbufpos >= term->wrbufused) {
2320 term->wrbufpos = term->wrbufused = 0;
2321 return;
2323 //dlogf("0: ttyflushwrbuf before: towrite=%d", term->wrbufused-term->wrbufpos);
2324 while (term->wrbufpos < term->wrbufused && ttycanwrite()) {
2325 int ret;
2327 if ((ret = write(term->cmdfd, term->wrbuf+term->wrbufpos, term->wrbufused-term->wrbufpos)) == -1) {
2328 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2330 term->wrbufpos += ret;
2332 if (term->wrbufpos > 0) {
2333 int left = term->wrbufused-term->wrbufpos;
2335 if (left < 1) {
2336 // write buffer is empty
2337 term->wrbufpos = term->wrbufused = 0;
2338 } else {
2339 memmove(term->wrbuf, term->wrbuf+term->wrbufpos, left);
2340 term->wrbufpos = 0;
2341 term->wrbufused = left;
2344 //dlogf("1: ttyflushwrbuf after: towrite=%d", term->wrbufused-term->wrbufpos);
2348 // convert char to locale and write it
2349 static void ttywriterawchar (const char *s, int len) {
2350 char loc[16];
2351 int clen;
2353 if (s == NULL || len < 1) return;
2354 if (term->needConv) {
2355 if ((clen = utf2loc(loc, s, len)) < 1) return;
2356 } else {
2357 if ((clen = utf8size(s)) < 1) return;
2358 memmove(loc, s, clen);
2360 #ifdef DUMP_IO_WRITE
2362 fprintf(stderr, "wrc: [");
2363 wrstr(s, len);
2364 fprintf(stderr, "] --> [");
2365 wrstr(loc, clen);
2366 fprintf(stderr, "]\n");
2367 fflush(stderr);
2369 #endif
2371 while (term->wrbufused+clen >= term->wrbufsize) {
2372 //FIXME: make write buffer dynamic?
2373 // force write at least one char
2374 //dlogf("ttywrite: forced write");
2375 if (write(term->cmdfd, term->wrbuf+term->wrbufpos, 1) == -1) {
2376 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2377 } else {
2378 ++term->wrbufpos;
2380 ttyflushwrbuf(); // make room for char
2382 memcpy(term->wrbuf+term->wrbufused, loc, clen);
2383 term->wrbufused += clen;
2387 static void ttywrite (const char *s, size_t n) {
2388 if (term == NULL || term->dead || term->cmdfd < 0) return;
2389 #ifdef DUMP_PROG_INPUT
2390 if (s != NULL && n > 0) {
2391 FILE *fo = fopen("zlogw.log", "ab");
2392 if (fo) {
2393 fwrite(s, n, 1, fo);
2394 fclose(fo);
2397 #endif
2398 //ttyflushwrbuf();
2399 if (s != NULL && n > 0) {
2400 while (n > 0) {
2401 unsigned char c = (unsigned char)(s[0]);
2403 if (term->ubufpos > 0 && isfullutf8(term->ubuf, term->ubufpos)) {
2404 // have complete char
2405 ttywriterawchar(term->ubuf, term->ubufpos);
2406 term->ubufpos = 0;
2407 continue;
2410 if (term->ubufpos == 0) {
2411 // new char
2412 if (c < 128) {
2413 ttywriterawchar(s, 1);
2414 } else if ((c&0xc0) == 0xc0) {
2415 // new utf-8 char
2416 term->ubuf[term->ubufpos++] = *s;
2417 } else {
2418 // ignore unsynced utf-8
2420 ++s;
2421 --n;
2422 continue;
2424 // char continues
2425 if (c < 128 || term->ubufpos >= UTF_SIZ || (c&0xc0) == 0xc0) {
2426 // discard previous utf-8, it's bad
2427 term->ubufpos = 0;
2428 continue;
2430 // collect
2431 term->ubuf[term->ubufpos++] = *s;
2432 ++s;
2433 --n;
2434 if (isfullutf8(term->ubuf, term->ubufpos)) {
2435 // have complete char
2436 ttywriterawchar(term->ubuf, term->ubufpos);
2437 term->ubufpos = 0;
2441 ttyflushwrbuf();
2445 ////////////////////////////////////////////////////////////////////////////////
2446 // tty resize ioctl
2447 static void ttyresize (void) {
2448 struct winsize w;
2450 if (term != NULL && term->cmdfd >= 0) {
2451 w.ws_row = term->row;
2452 w.ws_col = term->col;
2453 w.ws_xpixel = w.ws_ypixel = 0;
2454 if (ioctl(term->cmdfd, TIOCSWINSZ, &w) < 0) fprintf(stderr, "Warning: couldn't set window size: %s\n", SERRNO);
2455 setWantRedraw();
2460 ////////////////////////////////////////////////////////////////////////////////
2461 // tty utilities
2462 static void csidump (void) {
2463 printf("ESC");
2464 for (int f = 1; f < term->escseq.len; ++f) {
2465 uint c = (term->escseq.buf[f]&0xff);
2467 if (isprint(c)) putchar(c);
2468 else if (c == '\n') printf("(\\n)");
2469 else if (c == '\r') printf("(\\r)");
2470 else if (c == 0x1b) printf("(\\e)");
2471 else printf("(%02x)", c);
2473 putchar('\n');
2477 static void tsetdirt (int top, int bot) {
2478 LIMIT(top, 0, term->row-1);
2479 LIMIT(bot, 0, term->row-1);
2480 for (int y = top; y <= bot; ++y) markDirty(y, 2);
2484 static void tfulldirt (void) {
2485 tsetdirt(0, term->row-1);
2489 static void tmoveto (int x, int y) {
2490 LIMIT(x, 0, term->col-1);
2491 LIMIT(y, 0, term->row-1);
2492 term->c.state &= ~CURSOR_WRAPNEXT;
2493 if (term->c.x != x || term->c.y != y) {
2494 term->c.x = x;
2495 term->c.y = y;
2496 setWantRedraw();
2501 static void tclearregion (int x1, int y1, int x2, int y2) {
2502 int temp;
2504 //fprintf(stderr, "tclearregion: (%d,%d)-(%d,%d)\n", x1, y1, x2, y2);
2505 if (x1 > x2) { temp = x1; x1 = x2; x2 = temp; }
2506 if (y1 > y2) { temp = y1; y1 = y2; y2 = temp; }
2507 LIMIT(x1, 0, term->col-1);
2508 LIMIT(x2, 0, term->col-1);
2509 LIMIT(y1, 0, term->row-1);
2510 LIMIT(y2, 0, term->row-1);
2511 //fprintf(stderr, " tclearregion: (%d,%d)-(%d,%d)\n", x1, y1, x2, y2);
2512 for (int y = y1; y <= y2; ++y) {
2513 Line l = term->line[y];
2515 markDirty(y, (x1 <= 0 && x2 >= term->col-1) ? 2 : 1);
2516 for (int x = x1; x <= x2; ++x) {
2517 l[x].fg = term->c.attr.fg;
2518 l[x].bg = term->c.attr.bg;
2519 l[x].state = GLYPH_DIRTY;
2520 l[x].attr = ATTR_NULL|(term->c.attr.attr&(ATTR_DEFFG|ATTR_DEFBG));
2521 l[x].c[0] = ' ';
2522 if (term->sel.bx != -1 && selected(x, y)) selhide();
2524 l[term->col-1].state &= ~GLYPH_WRAP;
2529 static void tcursor (int mode) {
2530 if (mode == CURSOR_SAVE) {
2531 term->csaved = term->c;
2532 } else if (mode == CURSOR_LOAD) {
2533 term->c = term->csaved;
2534 tmoveto(term->c.x, term->c.y);
2535 setWantRedraw();
2540 static void treset (void) {
2541 Glyph g;
2543 term->c = (TCursor){{
2544 .attr = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG,
2545 .fg = 0,
2546 .bg = 0
2547 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
2548 term->c.attr.fg = term->deffg;
2549 term->c.attr.bg = term->defbg;
2551 g.state = GLYPH_DIRTY;
2552 g.attr = term->c.attr.attr;
2553 g.fg = term->c.attr.fg;
2554 g.bg = term->c.attr.bg;
2555 g.c[0] = ' ';
2556 g.c[1] = 0;
2558 term->top = 0;
2559 term->bot = term->row-1;
2560 term->mode = MODE_WRAP/* | MODE_MOUSEBTN*/;
2561 term->mousemode = 1000;
2562 term->charset = MODE_GFX0;
2563 //tclearregion(0, 0, term->col-1, term->row-1);
2564 for (int y = 0; y < term->row; ++y) {
2565 markDirty(y, 2);
2566 for (int x = 0; x < term->col; ++x) term->alt[y][x] = term->line[y][x] = g;
2568 for (int y = term->row; y < term->linecount; ++y) {
2569 for (int x = 0; x < term->col; ++x) term->line[y][x] = g;
2571 tcursor(CURSOR_SAVE);
2572 term->topline = 0;
2573 tfulldirt();
2577 static int tinitialize (int col, int row) {
2578 //memset(term, 0, sizeof(Term));
2579 //term->needConv = needConversion ? 1 : 0;
2580 term->wrbufsize = WBUFSIZ;
2581 term->deffg = term->deffg;
2582 term->defbg = term->defbg;
2583 term->row = row;
2584 term->col = col;
2585 term->dirty = calloc(term->row, sizeof(*term->dirty));
2586 term->maxhistory = opt_maxhistory;
2587 term->linecount = term->maxhistory+term->row;
2588 term->line = calloc(term->linecount, sizeof(Line));
2589 term->alt = calloc(term->row, sizeof(Line));
2590 for (int y = 0; y < term->linecount; ++y) term->line[y] = calloc(term->col, sizeof(Glyph));
2591 for (int y = 0; y < term->row; ++y) term->alt[y] = calloc(term->col, sizeof(Glyph));
2592 /* setup screen */
2593 treset();
2594 return 1;
2598 static void tadjustmaxhistory (int maxh) {
2599 if (term != NULL) {
2600 LIMIT(maxh, 0, 65535);
2601 if (term->maxhistory < maxh) {
2602 Line *nl;
2603 int newlc = term->linecount+(maxh-term->maxhistory);
2605 // add history lines
2606 if ((nl = realloc(term->line, sizeof(Line)*newlc)) != NULL) {
2607 Glyph g;
2609 term->topline = 0;
2610 term->line = nl;
2611 g.state = GLYPH_DIRTY;
2612 g.attr = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG;
2613 g.fg = term->deffg;
2614 g.bg = term->defbg;
2615 g.c[0] = ' ';
2616 g.c[1] = 0;
2617 for (int y = term->linecount; y < newlc; ++y) {
2618 term->line[y] = calloc(term->col, sizeof(Glyph));
2619 for (int x = 0; x < term->col; ++x) term->line[y][x] = g;
2621 term->maxhistory = maxh;
2622 term->linecount = newlc;
2624 } else if (term->maxhistory > maxh) {
2625 Line *nl;
2627 term->topline = 0;
2628 // remove history lines
2629 while (term->linecount > term->row+maxh) free(term->line[--term->linecount]);
2630 if ((nl = realloc(term->line, sizeof(Line)*term->linecount)) != NULL) term->line = nl;
2631 term->maxhistory = maxh;
2637 static void tswapscreen (void) {
2638 selhide();
2639 for (int f = 0; f < term->row; ++f) {
2640 Line t = term->line[f];
2642 term->line[f] = term->alt[f];
2643 term->alt[f] = t;
2645 term->mode ^= MODE_ALTSCREEN;
2646 tfulldirt();
2650 //FIXME: works bad with history
2651 //FIXME: ugly code
2652 static void selscroll (int orig, int n, int tohistory) {
2653 int docopy = 0;
2655 if (term->sel.bx == -1) return;
2657 tfulldirt(); // just in case
2658 if (!tohistory) {
2659 if (BETWEEN(term->sel.by, orig, term->bot) || BETWEEN(term->sel.ey, orig, term->bot)) {
2660 if ((term->sel.by += n) > term->bot || (term->sel.ey += n) < term->top) {
2661 selclear(NULL);
2662 return;
2664 if (term->sel.by < term->top) {
2665 term->sel.by = term->top;
2666 term->sel.bx = 0;
2667 docopy = 1;
2669 if (term->sel.ey > term->bot) {
2670 term->sel.ey = term->bot;
2671 term->sel.ex = term->col;
2672 docopy = 1;
2674 term->sel.b.x = term->sel.bx;
2675 term->sel.b.y = term->sel.by;
2676 term->sel.e.x = term->sel.ex;
2677 term->sel.e.y = term->sel.ey;
2679 } else {
2680 // tohistory!=0; always scrolls full screen up (n == -1)
2681 //fprintf(stderr, "selscroll to history\n");
2682 term->sel.by += n;
2683 term->sel.ey += n;
2684 //fprintf(stderr, " by=%d; ey=%d; maxhistory=%d\n", term->sel.by, term->sel.ey, term->maxhistory);
2685 if (term->sel.ey < 0 && -(term->sel.ey) > term->maxhistory) {
2686 // out of screen completely
2687 selclear(NULL);
2688 return;
2690 if (term->sel.by < 0 && -(term->sel.by) > term->maxhistory) {
2691 term->sel.by = -term->maxhistory;
2692 term->sel.bx = 0;
2693 docopy = 1;
2695 term->sel.b.x = term->sel.bx;
2696 term->sel.b.y = term->sel.by;
2697 term->sel.e.x = term->sel.ex;
2698 term->sel.e.y = term->sel.ey;
2701 if (docopy) selcopy();
2705 static void tscrolldown (int orig, int n) {
2706 Line temp;
2708 LIMIT(n, 0, term->bot-orig+1);
2709 if (n < 1) return;
2710 selscroll(orig, n, 0);
2711 //fprintf(stderr, "tscrolldown(%d, %d)\n", orig, n);
2712 tclearregion(0, term->bot-n+1, term->col-1, term->bot);
2713 for (int f = term->bot; f >= orig+n; --f) {
2714 temp = term->line[f];
2715 term->line[f] = term->line[f-n];
2716 term->line[f-n] = temp;
2717 markDirty(f, 2);
2718 markDirty(f-n, 2);
2723 static void tscrollup (int orig, int n, int tohistory) {
2724 Line temp;
2726 if (term == NULL) return;
2727 LIMIT(n, 0, term->bot-orig+1);
2728 if (n < 1) return;
2729 //fprintf(stderr, "tscrollup(%d, %d)\n", orig, n);
2730 if (tohistory && !IS_SET(MODE_ALTSCREEN) && term->maxhistory > 0) {
2731 Line l = term->line[term->linecount-1];
2733 for (int f = term->linecount-1; f > term->row; --f) term->line[f] = term->line[f-1];
2734 term->line[term->row] = l;
2735 for (int x = 0; x < term->col; ++x) l[x] = term->line[0][x];
2736 } else {
2737 tohistory = 0;
2740 selscroll(orig, -n, tohistory);
2741 //tclearregion(0, orig, term->col-1, orig+n-1);
2742 for (int f = orig; f <= term->bot-n; ++f) {
2743 temp = term->line[f];
2744 term->line[f] = term->line[f+n];
2745 term->line[f+n] = temp;
2746 markDirty(f, 2);
2747 markDirty(f+n, 2);
2749 tclearregion(0, term->bot-n+1, term->col-1, term->bot);
2753 static inline void tsetcharwrap (int y, int wrap) {
2754 if (y >= 0 && y < term->row) {
2755 if (wrap) term->line[y][term->col-1].state |= GLYPH_WRAP;
2756 else term->line[y][term->col-1].state &= ~GLYPH_WRAP;
2761 static void tnewline (int first_col) {
2762 int y = term->c.y;
2764 tsetcharwrap(y, (first_col == 2)); // 2: wrapping
2765 if (y == term->bot) tscrollup(term->top, 1, 1); else ++y;
2766 tmoveto(first_col ? 0 : term->c.x, y);
2770 static void csiparse (void) {
2771 const char *p = term->escseq.buf;
2773 term->escseq.narg = 0;
2774 if (*p == '?') { term->escseq.priv = 1; ++p; }
2775 while (p < term->escseq.buf+term->escseq.len) {
2776 int n = term->escseq.arg[term->escseq.narg];
2778 for (; *p && isdigit(*p); ++p) n = n*10+(p[0]-'0');
2779 term->escseq.arg[term->escseq.narg] = n;
2781 if (*p == ';' && term->escseq.narg+1 < ESC_ARG_SIZ) {
2782 ++term->escseq.narg;
2783 ++p;
2784 } else {
2785 term->escseq.mode = *p;
2786 ++term->escseq.narg;
2787 break;
2793 static void tsetchar (const char *c) {
2794 char ub[UTF_SIZ+1];
2795 int rev = 0, gfx = 0;
2796 int x = term->c.x, y = term->c.y;
2798 if (x < 0 || x >= term->col || y < 0 || y >= term->row) return;
2800 if (!term->needConv && unimap != NULL) {
2801 ulong cc;
2803 utf8decode(c, &cc);
2804 if (cc <= 65535) {
2805 ushort uc = unimap[cc];
2807 if (uc) {
2808 if (uc == 127) {
2809 // inversed space
2810 rev = 1;
2811 ub[0] = ' ';
2812 //ub[1] = 0;
2813 } else {
2814 if (uc&0x8000) {
2815 ub[0] = (uc&0x7f);
2816 gfx = 1;
2817 } else {
2818 //ub[0] = uc&0x7f;
2819 utf8encode(uc, ub);
2822 c = ub;
2826 markDirty(y, 1);
2828 term->line[y][x] = term->c.attr;
2829 if (rev) term->line[y][x].attr ^= ATTR_REVERSE;
2830 if (gfx || (term->mode&term->charset)) {
2831 term->line[y][x].attr |= ATTR_GFX;
2832 } else {
2833 term->line[y][x].attr &= ~ATTR_GFX;
2836 term->line[y][x].state = (GLYPH_SET | GLYPH_DIRTY);
2837 memmove(term->line[y][x].c, c, UTF_SIZ);
2839 if (IS_GFX(term->line[y][x].attr)) {
2840 unsigned char c = (unsigned char)(term->line[y][x].c[0]);
2842 if (c > 95 && c < 128) term->line[y][x].c[0] -= 95;
2843 else if (c > 127) term->line[y][x].c[0] = ' ';
2845 if (term->sel.bx != -1 && selected(x, y)) selhide();
2846 //dlogf("tsetchar(%d,%d): [%c] (%d); dirty=%d\n", x, y, c[0], term->wantRedraw, term->dirty[y]);
2850 static void tdeletechar (int n) {
2851 int src = term->c.x+n;
2852 int dst = term->c.x;
2853 int size = term->col-src;
2855 markDirty(term->c.y, 2);
2856 if (src >= term->col) {
2857 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2858 } else {
2859 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2860 tclearregion(term->col-n, term->c.y, term->col-1, term->c.y);
2865 static void tinsertblank (int n) {
2866 int src = term->c.x;
2867 int dst = src+n;
2868 int size = term->col-dst;
2870 markDirty(term->c.y, 2);
2871 if (dst >= term->col) {
2872 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2873 } else {
2874 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2875 tclearregion(src, term->c.y, dst-1, term->c.y);
2880 static void tinsertblankline (int n) {
2881 if (term->c.y < term->top || term->c.y > term->bot) return;
2882 tscrolldown(term->c.y, n);
2886 static void tdeleteline (int n) {
2887 if (term->c.y < term->top || term->c.y > term->bot) return;
2888 tscrollup(term->c.y, n, 0);
2892 static void tsetattr (int *attr, int l) {
2893 for (int f = 0; f < l; ++f) {
2894 switch (attr[f]) {
2895 case 0:
2896 term->c.attr.attr &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
2897 term->c.attr.attr |= ATTR_DEFFG | ATTR_DEFBG;
2898 term->c.attr.fg = term->deffg;
2899 term->c.attr.bg = term->defbg;
2900 break;
2901 case 1:
2902 term->c.attr.attr |= ATTR_BOLD;
2903 break;
2904 case 4:
2905 term->c.attr.attr |= ATTR_UNDERLINE;
2906 break;
2907 case 7:
2908 term->c.attr.attr |= ATTR_REVERSE;
2909 break;
2910 case 22:
2911 term->c.attr.attr &= ~ATTR_BOLD;
2912 break;
2913 case 24:
2914 term->c.attr.attr &= ~ATTR_UNDERLINE;
2915 break;
2916 case 27:
2917 term->c.attr.attr &= ~ATTR_REVERSE;
2918 break;
2919 case 38:
2920 if (f+2 < l && attr[f+1] == 5) {
2921 f += 2;
2922 if (BETWEEN(attr[f], 0, 255)) {
2923 term->c.attr.fg = attr[f];
2924 term->c.attr.attr &= ~ATTR_DEFFG;
2925 } else {
2926 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[f]);
2928 } else {
2929 //fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2930 term->c.attr.fg = term->deffg;
2931 term->c.attr.attr |= ATTR_DEFFG;
2933 break;
2934 case 39:
2935 term->c.attr.fg = term->deffg;
2936 term->c.attr.attr |= ATTR_DEFFG;
2937 break;
2938 case 48:
2939 if (f+2 < l && attr[f+1] == 5) {
2940 f += 2;
2941 if (BETWEEN(attr[f], 0, 255)) {
2942 term->c.attr.bg = attr[f];
2943 term->c.attr.attr &= ~ATTR_DEFBG;
2944 } else {
2945 fprintf(stderr, "erresc: bad bgcolor %d\n", attr[f]);
2947 } else {
2948 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2950 break;
2951 case 49:
2952 term->c.attr.bg = term->defbg;
2953 term->c.attr.attr |= ATTR_DEFBG;
2954 break;
2955 default:
2956 if (BETWEEN(attr[f], 30, 37)) { term->c.attr.fg = attr[f]-30; term->c.attr.attr &= ~ATTR_DEFFG; }
2957 else if (BETWEEN(attr[f], 40, 47)) { term->c.attr.bg = attr[f]-40; term->c.attr.attr &= ~ATTR_DEFBG; }
2958 else if (BETWEEN(attr[f], 90, 97)) { term->c.attr.fg = attr[f]-90+8; term->c.attr.attr &= ~ATTR_DEFFG; }
2959 else if (BETWEEN(attr[f], 100, 107)) { term->c.attr.bg = attr[f]-100+8; term->c.attr.attr &= ~ATTR_DEFBG; }
2960 else { fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]); csidump(); }
2961 break;
2967 static void tsetscroll (int t, int b) {
2968 int temp;
2970 LIMIT(t, 0, term->row-1);
2971 LIMIT(b, 0, term->row-1);
2972 if (t > b) {
2973 temp = t;
2974 t = b;
2975 b = temp;
2977 term->top = t;
2978 term->bot = b;
2982 ////////////////////////////////////////////////////////////////////////////////
2983 // esc processing
2984 static void csihandle (void) {
2985 switch (term->escseq.mode) {
2986 case '@': /* ICH -- Insert <n> blank char */
2987 DEFAULT(term->escseq.arg[0], 1);
2988 tinsertblank(term->escseq.arg[0]);
2989 break;
2990 case 'A': /* CUU -- Cursor <n> Up */
2991 case 'e':
2992 DEFAULT(term->escseq.arg[0], 1);
2993 tmoveto(term->c.x, term->c.y-term->escseq.arg[0]);
2994 break;
2995 case 'B': /* CUD -- Cursor <n> Down */
2996 DEFAULT(term->escseq.arg[0], 1);
2997 tmoveto(term->c.x, term->c.y+term->escseq.arg[0]);
2998 break;
2999 case 'C': /* CUF -- Cursor <n> Forward */
3000 case 'a':
3001 DEFAULT(term->escseq.arg[0], 1);
3002 tmoveto(term->c.x+term->escseq.arg[0], term->c.y);
3003 break;
3004 case 'D': /* CUB -- Cursor <n> Backward */
3005 DEFAULT(term->escseq.arg[0], 1);
3006 tmoveto(term->c.x-term->escseq.arg[0], term->c.y);
3007 break;
3008 case 'E': /* CNL -- Cursor <n> Down and first col */
3009 DEFAULT(term->escseq.arg[0], 1);
3010 tmoveto(0, term->c.y+term->escseq.arg[0]);
3011 break;
3012 case 'F': /* CPL -- Cursor <n> Up and first col */
3013 DEFAULT(term->escseq.arg[0], 1);
3014 tmoveto(0, term->c.y-term->escseq.arg[0]);
3015 break;
3016 case 'G': /* CHA -- Move to <col> */
3017 case '`': /* XXX: HPA -- same? */
3018 DEFAULT(term->escseq.arg[0], 1);
3019 tmoveto(term->escseq.arg[0]-1, term->c.y);
3020 break;
3021 case 'H': /* CUP -- Move to <row> <col> */
3022 case 'f': /* XXX: HVP -- same? */
3023 DEFAULT(term->escseq.arg[0], 1);
3024 DEFAULT(term->escseq.arg[1], 1);
3025 tmoveto(term->escseq.arg[1]-1, term->escseq.arg[0]-1);
3026 break;
3027 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
3028 case 'J': /* ED -- Clear screen */
3029 term->sel.bx = -1;
3030 switch (term->escseq.arg[0]) {
3031 case 0: /* below */
3032 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
3033 if (term->c.y < term->row-1) tclearregion(0, term->c.y+1, term->col-1, term->row-1);
3034 break;
3035 case 1: /* above */
3036 if (term->c.y > 1) tclearregion(0, 0, term->col-1, term->c.y-1);
3037 tclearregion(0, term->c.y, term->c.x, term->c.y);
3038 break;
3039 case 2: /* all */
3040 tclearregion(0, 0, term->col-1, term->row-1);
3041 break;
3042 default:
3043 goto unknown;
3045 break;
3046 case 'K': /* EL -- Clear line */
3047 switch (term->escseq.arg[0]) {
3048 case 0: /* right */
3049 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
3050 break;
3051 case 1: /* left */
3052 tclearregion(0, term->c.y, term->c.x, term->c.y);
3053 break;
3054 case 2: /* all */
3055 tclearregion(0, term->c.y, term->col-1, term->c.y);
3056 break;
3058 break;
3059 case 'S': /* SU -- Scroll <n> line up */
3060 DEFAULT(term->escseq.arg[0], 1);
3061 tscrollup(term->top, term->escseq.arg[0], 0);
3062 break;
3063 case 'T': /* SD -- Scroll <n> line down */
3064 DEFAULT(term->escseq.arg[0], 1);
3065 tscrolldown(term->top, term->escseq.arg[0]);
3066 break;
3067 case 'L': /* IL -- Insert <n> blank lines */
3068 DEFAULT(term->escseq.arg[0], 1);
3069 tinsertblankline(term->escseq.arg[0]);
3070 break;
3071 case 'l': /* RM -- Reset Mode */
3072 if (term->escseq.priv) {
3073 switch (term->escseq.arg[0]) {
3074 case 1: // 1001 for xterm compatibility
3075 DUMP_KEYPAD_SWITCH("1", "OFF");
3076 term->mode &= ~MODE_APPKEYPAD;
3077 break;
3078 case 5: /* DECSCNM -- Remove reverse video */
3079 if (IS_SET(MODE_REVERSE)) {
3080 term->mode &= ~MODE_REVERSE;
3081 tfulldirt();
3083 break;
3084 case 7: /* autowrap off */
3085 term->mode &= ~MODE_WRAP;
3086 break;
3087 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
3088 break;
3089 case 20: /* non-standard code? */
3090 term->mode &= ~MODE_CRLF;
3091 break;
3092 case 25: /* hide cursor */
3093 if ((term->c.state&CURSOR_HIDE) == 0) {
3094 term->c.state |= CURSOR_HIDE;
3095 term->wantRedraw = 1;
3097 break;
3098 case 1000: /* disable X11 xterm mouse reporting */
3099 term->mode &= ~MODE_MOUSEBTN;
3100 break;
3101 case 1002:
3102 term->mode &= ~MODE_MOUSEMOTION;
3103 break;
3104 case 1004:
3105 term->mode &= ~MODE_FOCUSEVT;
3106 break;
3107 case 1005: /* utf-8 mouse encoding */
3108 case 1006: /* sgr mouse encoding */
3109 case 1015: /* urxvt mouse encoding */
3110 term->mousemode = 1000;
3111 break;
3112 case 1049: /* = 1047 and 1048 */
3113 case 47:
3114 case 1047:
3115 if (IS_SET(MODE_ALTSCREEN)) {
3116 tclearregion(0, 0, term->col-1, term->row-1);
3117 tswapscreen();
3119 if (term->escseq.arg[0] != 1049) break;
3120 case 1048:
3121 tcursor(CURSOR_LOAD);
3122 break;
3123 case 2004: /* reset bracketed paste mode */
3124 term->mode &= ~MODE_BRACPASTE;
3125 break;
3126 default:
3127 goto unknown;
3129 } else {
3130 switch (term->escseq.arg[0]) {
3131 case 3:
3132 term->mode &= ~MODE_DISPCTRL;
3133 break;
3134 case 4:
3135 term->mode &= ~MODE_INSERT;
3136 break;
3137 default:
3138 goto unknown;
3141 break;
3142 case 'M': /* DL -- Delete <n> lines */
3143 DEFAULT(term->escseq.arg[0], 1);
3144 tdeleteline(term->escseq.arg[0]);
3145 break;
3146 case 'X': /* ECH -- Erase <n> char */
3147 DEFAULT(term->escseq.arg[0], 1);
3148 tclearregion(term->c.x, term->c.y, term->c.x + term->escseq.arg[0], term->c.y);
3149 break;
3150 case 'P': /* DCH -- Delete <n> char */
3151 DEFAULT(term->escseq.arg[0], 1);
3152 tdeletechar(term->escseq.arg[0]);
3153 break;
3154 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
3155 case 'd': /* VPA -- Move to <row> */
3156 DEFAULT(term->escseq.arg[0], 1);
3157 tmoveto(term->c.x, term->escseq.arg[0]-1);
3158 break;
3159 case 'h': /* SM -- Set terminal mode */
3160 if (term->escseq.priv) {
3161 switch (term->escseq.arg[0]) {
3162 case 1:
3163 DUMP_KEYPAD_SWITCH("1", "ON");
3164 term->mode |= MODE_APPKEYPAD;
3165 break;
3166 case 5: /* DECSCNM -- Reverve video */
3167 if (!IS_SET(MODE_REVERSE)) {
3168 term->mode |= MODE_REVERSE;
3169 tfulldirt();
3171 break;
3172 case 7:
3173 term->mode |= MODE_WRAP;
3174 break;
3175 case 20:
3176 term->mode |= MODE_CRLF;
3177 break;
3178 case 12: /* att610 -- Start blinking cursor (IGNORED) */
3179 /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
3180 if (term->escseq.narg > 1 && term->escseq.arg[1] != 25) break;
3181 case 25:
3182 if ((term->c.state&CURSOR_HIDE) != 0) {
3183 term->c.state &= ~CURSOR_HIDE;
3184 term->wantRedraw = 1;
3186 break;
3187 case 1000: /* 1000,1002: enable xterm mouse report */
3188 term->mode |= MODE_MOUSEBTN;
3189 break;
3190 case 1002:
3191 term->mode |= MODE_MOUSEMOTION;
3192 break;
3193 case 1004:
3194 term->mode |= MODE_FOCUSEVT;
3195 break;
3196 case 1005: /* utf-8 mouse encoding */
3197 case 1006: /* sgr mouse encoding */
3198 case 1015: /* urxvt mouse encoding */
3199 term->mousemode = term->escseq.arg[0];
3200 break;
3201 case 1049: /* = 1047 and 1048 */
3202 case 47:
3203 case 1047:
3204 if (IS_SET(MODE_ALTSCREEN)) tclearregion(0, 0, term->col-1, term->row-1); else tswapscreen();
3205 if (term->escseq.arg[0] != 1049) break;
3206 case 1048:
3207 tcursor(CURSOR_SAVE);
3208 break;
3209 case 2004: /* set bracketed paste mode */
3210 term->mode |= MODE_BRACPASTE;
3211 break;
3212 default: goto unknown;
3214 } else {
3215 switch (term->escseq.arg[0]) {
3216 case 3:
3217 term->mode |= MODE_DISPCTRL;
3218 break;
3219 case 4:
3220 term->mode |= MODE_INSERT;
3221 break;
3222 default:
3223 goto unknown;
3226 break;
3227 case 'm': /* SGR -- Terminal attribute (color) */
3228 tsetattr(term->escseq.arg, term->escseq.narg);
3229 break;
3230 case 'n':
3231 if (!term->escseq.priv) {
3232 switch (term->escseq.arg[0]) {
3233 case 6: { /* cursor position report */
3234 char buf[32];
3236 sprintf(buf, "\x1b[%d;%dR", term->c.x+1, term->c.y+1);
3237 ttywritestr(buf);
3238 } break;
3241 break;
3242 case 'r': /* DECSTBM -- Set Scrolling Region */
3243 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
3244 // xterm compatibility
3245 DUMP_KEYPAD_SWITCH("1001", "OFF");
3246 term->mode &= ~MODE_APPKEYPAD;
3247 } else if (term->escseq.priv) {
3248 goto unknown;
3249 } else {
3250 DEFAULT(term->escseq.arg[0], 1);
3251 DEFAULT(term->escseq.arg[1], term->row);
3252 tsetscroll(term->escseq.arg[0]-1, term->escseq.arg[1]-1);
3253 tmoveto(0, 0);
3255 break;
3256 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
3257 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
3258 // xterm compatibility
3259 DUMP_KEYPAD_SWITCH("1001", "ON");
3260 term->mode |= MODE_APPKEYPAD;
3261 } else {
3262 tcursor(CURSOR_SAVE);
3264 break;
3265 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
3266 tcursor(CURSOR_LOAD);
3267 break;
3268 default:
3269 unknown:
3270 fprintf(stderr, "erresc: unknown csi ");
3271 csidump();
3272 break;
3277 static void csireset (void) {
3278 memset(&term->escseq, 0, sizeof(term->escseq));
3282 static void tputtab (void) {
3283 int space = opt_tabsize-term->c.x%opt_tabsize;
3285 if (space > 0) tmoveto(term->c.x+space, term->c.y);
3289 ////////////////////////////////////////////////////////////////////////////////
3290 // put char to output buffer or process command
3292 // return 1 if this was control character
3293 // return -1 if this should break esape sequence
3294 static int tputc_ctrl (char ascii) {
3295 int res = 1;
3297 if (term->esc&ESC_TITLE) return 0;
3299 switch (ascii) {
3300 case '\t': tputtab(); break;
3301 case '\b': tmoveto(term->c.x-1, term->c.y); break;
3302 case '\r': tmoveto(0, term->c.y); break;
3303 case '\f': case '\n': case '\v': tnewline(IS_SET(MODE_CRLF)?1:0); break; /* go to first col if the mode is set */
3304 case '\a':
3305 if (!(xw.state & WIN_FOCUSED) && (term->belltype&BELL_URGENT)) xseturgency(1);
3306 if (term->belltype&BELL_AUDIO) XBell(xw.dpy, 100);
3307 break;
3308 case 14: term->charset = MODE_GFX1; break;
3309 case 15: term->charset = MODE_GFX0; break;
3310 case 0x18: case 0x1a: res = -1; break; // do nothing, interrupt current escape sequence
3311 case 127: break; // ignore it
3312 case '\033': csireset(); term->esc = ESC_START; break;
3313 //case 0x9b: csireset(); term->esc = ESC_START | ESC_CSI; break;
3314 default: res = 0; break;
3316 return res;
3320 static void tputc (const char *c) {
3321 char ascii = *c;
3322 int ctl = tputc_ctrl(ascii);
3324 if (ctl > 0) return; // control char; should not break escape sequence
3325 if (ctl < 0) {
3326 // control char; should break escape sequence
3327 term->esc = 0;
3328 return;
3330 //dlogf("tputc: [%c]\n", c[0]);
3331 if (term->esc & ESC_START) {
3332 if (term->esc & ESC_CSI) {
3333 term->escseq.buf[term->escseq.len++] = ascii;
3334 if (BETWEEN(ascii, 0x40, 0x7E) || term->escseq.len >= ESC_BUF_SIZ) {
3335 term->esc = 0;
3336 csiparse();
3337 csihandle();
3339 } else if (term->esc & ESC_OSC) {
3340 /* TODO: handle other OSC */
3341 if (ascii == ';') {
3342 term->title[0] = 0;
3343 term->titlelen = 0;
3344 term->esc = ESC_START | ESC_TITLE;
3345 //updateTabBar = 1;
3347 } else if (term->esc & ESC_TITLE) {
3348 int len = utf8size(c);
3350 if (ascii == '\a' || term->titlelen+len >= ESC_TITLE_SIZ) {
3351 term->esc = 0;
3352 term->title[term->titlelen] = '\0';
3353 fixWindowTitle(term);
3354 updateTabBar = 1;
3355 } else if (len > 0) {
3356 memcpy(term->title+term->titlelen, c, len);
3357 term->titlelen += len;
3358 term->title[term->titlelen] = '\0';
3360 } else if (term->esc & ESC_ALTCHARSET) {
3361 term->esc = 0;
3362 switch (ascii) {
3363 case '0': /* Line drawing crap */
3364 term->mode |= MODE_GFX0;
3365 break;
3366 case 'B': /* Back to regular text */
3367 term->mode &= ~MODE_GFX0;
3368 break;
3369 default:
3370 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
3371 term->mode &= ~MODE_GFX0;
3372 break;
3374 } else if (term->esc & ESC_ALTG1) {
3375 term->esc = 0;
3376 switch (ascii) {
3377 case '0': /* Line drawing crap */
3378 term->mode |= MODE_GFX1;
3379 break;
3380 case 'B': /* Back to regular text */
3381 term->mode &= ~MODE_GFX1;
3382 break;
3383 default:
3384 fprintf(stderr, "esc unhandled charset: ESC ) %c\n", ascii);
3385 term->mode &= ~MODE_GFX1;
3386 break;
3388 } else if (term->esc & ESC_HASH) {
3389 term->esc = 0;
3390 switch (ascii) {
3391 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
3392 //tfillscreenwithE();
3393 break;
3395 } else if (term->esc & ESC_PERCENT) {
3396 term->esc = 0;
3397 } else {
3398 switch (ascii) {
3399 case '[': term->esc |= ESC_CSI; break;
3400 case ']': term->esc |= ESC_OSC; break;
3401 case '(': term->esc |= ESC_ALTCHARSET; break;
3402 case ')': term->esc |= ESC_ALTG1; break;
3403 case '#': term->esc |= ESC_HASH; break;
3404 case '%': term->esc |= ESC_PERCENT; break;
3405 case 'D': /* IND -- Linefeed */
3406 term->esc = 0;
3407 if (term->c.y == term->bot) tscrollup(term->top, 1, 1); else tmoveto(term->c.x, term->c.y+1);
3408 break;
3409 case 'E': /* NEL -- Next line */
3410 term->esc = 0;
3411 tnewline(1); /* always go to first col */
3412 break;
3413 case 'M': /* RI -- Reverse linefeed */
3414 term->esc = 0;
3415 if (term->c.y == term->top) tscrolldown(term->top, 1); else tmoveto(term->c.x, term->c.y-1);
3416 break;
3417 case 'c': /* RIS -- Reset to inital state */
3418 term->esc = 0;
3419 treset();
3420 break;
3421 case '=': /* DECPAM -- Application keypad */
3422 DUMP_KEYPAD_SWITCH("=", "ON");
3423 term->esc = 0;
3424 term->mode |= MODE_APPKEYPAD;
3425 break;
3426 case '>': /* DECPNM -- Normal keypad */
3427 DUMP_KEYPAD_SWITCH(">", "OFF");
3428 term->esc = 0;
3429 term->mode &= ~MODE_APPKEYPAD;
3430 break;
3431 case '7': /* DECSC -- Save Cursor */
3432 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
3433 //TODO?
3434 term->esc = 0;
3435 tcursor(CURSOR_SAVE);
3436 break;
3437 case '8': /* DECRC -- Restore Cursor */
3438 //TODO?
3439 term->esc = 0;
3440 tcursor(CURSOR_LOAD);
3441 break;
3442 case 'Z': /* DEC private identification */
3443 term->esc = 0;
3444 ttywritestr("\x1b[?1;2c");
3445 break;
3446 default:
3447 term->esc = 0;
3448 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X ('%c')\n", (uchar)ascii, isprint(ascii)?ascii:'.');
3449 break;
3452 } else {
3453 //if (term->sel.bx != -1 && BETWEEN(term->c.y, term->sel.by, term->sel.ey)) term->sel.bx = -1;
3454 do {
3455 if (term->needConv && IS_GFX(term->c.attr.attr)) {
3456 ulong cc;
3458 utf8decode(c, &cc);
3459 if (cc < 32 || cc >= 127) break; //FIXME: nothing at all?
3460 } else {
3461 if ((unsigned char)ascii < 32 || ascii == 127) break; // seems that this chars are empty too
3463 if (term->c.state&CURSOR_WRAPNEXT) {
3464 if (IS_SET(MODE_WRAP)) {
3465 // always go to first col
3466 tnewline(2);
3467 } else {
3468 tsetcharwrap(term->c.y, 0);
3469 break; // wrap is off, don't want more chars
3472 tsetchar(c);
3473 if (term->c.x+1 < term->col) tmoveto(term->c.x+1, term->c.y); else term->c.state |= CURSOR_WRAPNEXT;
3474 } while (0);
3479 static void tunshowhistory (void) {
3480 if (term != NULL && term->topline != 0) {
3481 term->topline = 0;
3482 term->wantRedraw = 1;
3483 term->lastDrawTime = 0;
3488 static void tsendfocusevent (int focused) {
3489 if (term != NULL && IS_SET(MODE_FOCUSEVT)) {
3490 ttywritestr("\x1b[");
3491 ttywrite(focused?"I":"O", 1);
3496 static void tcmdlinedirty (void) {
3497 if (term != NULL) {
3498 markDirty(term->row-term->topline-1, 2);
3499 term->wantRedraw = 1;
3504 static void tcmdlinefixofs (void) {
3505 int ofs, len;
3507 len = utf8strlen(term->cmdline);
3508 ofs = len-(term->col-1);
3509 if (ofs < 0) ofs = 0;
3510 for (term->cmdofs = 0; ofs > 0; --ofs) term->cmdofs += utf8size(term->cmdline+term->cmdofs);
3511 tcmdlinedirty();
3515 static void tcmdlinehide (void) {
3516 term->cmdMode = CMDMODE_NONE;
3517 term->cmdprevc = NULL;
3518 tcmdlinedirty();
3522 // utf-8
3523 static void tcmdlinemsg (const char *msg) {
3524 if (msg != NULL) {
3525 int ofs = 0;
3527 term->cmdMode = CMDMODE_MESSAGE;
3528 term->cmdofs = 0;
3529 term->cmdtabpos = -1;
3530 term->cmdprevc = NULL;
3532 while (*msg) {
3533 int len = utf8size(msg);
3535 if (len < 1 || ofs+len >= sizeof(term->cmdline)-1) break;
3536 memcpy(term->cmdline+ofs, msg, len);
3537 ofs += len;
3538 msg += len;
3541 term->cmdline[ofs] = 0;
3542 tcmdlinedirty();
3547 static void tcmdlineinitex (const char *msg) {
3548 term->cmdMode = CMDMODE_INPUT;
3549 term->cmdofs = 0;
3550 term->cmdline[0] = 0;
3551 term->cmdc[0] = 0;
3552 term->cmdcl = 0;
3553 term->cmdtabpos = -1;
3554 term->cmdprevc = NULL;
3555 term->cmdreslen = 0;
3556 term->cmdexecfn = NULL;
3557 if (msg != NULL && msg[0]) {
3558 strcpy(term->cmdline, msg);
3559 term->cmdreslen = strlen(term->cmdline);
3561 tcmdlinefixofs();
3565 static void tcmdlineinit (void) {
3566 tcmdlineinitex(NULL);
3570 static void tcmdlinechoplast (void) {
3571 if (term->cmdcl != 0) {
3572 term->cmdcl = 0;
3573 } else {
3574 if (strlen(term->cmdline) > term->cmdreslen) utf8choplast(term->cmdline);
3576 tcmdlinefixofs();
3580 // utf-8
3581 static void tcmdaddchar (const char *s) {
3582 int len = utf8size(s);
3584 if (len > 0) {
3585 int slen = strlen(term->cmdline);
3587 if (slen+len < sizeof(term->cmdline)) {
3588 memcpy(term->cmdline+slen, s, len);
3589 term->cmdline[slen+len] = 0;
3590 tcmdlinefixofs();
3596 static void tcmdput (const char *s, int len) {
3597 while (len-- > 0) {
3598 int ok;
3600 term->cmdc[term->cmdcl++] = *s++;
3601 term->cmdc[term->cmdcl] = 0;
3603 if ((ok = isfullutf8(term->cmdc, term->cmdcl)) != 0 || term->cmdcl == UTF_SIZ) {
3604 if (ok) tcmdaddchar(term->cmdc);
3605 term->cmdcl = 0;
3611 ////////////////////////////////////////////////////////////////////////////////
3612 // tty resising
3613 static int tresize (int col, int row) {
3614 int mincol = MIN(col, term->col);
3615 int slide = term->c.y-row+1;
3616 Glyph g;
3618 if (col < 1 || row < 1) return 0;
3620 selhide();
3622 g.state = GLYPH_DIRTY;
3623 g.attr = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG;
3624 g.fg = term->deffg;
3625 g.bg = term->defbg;
3626 g.c[0] = ' ';
3627 g.c[1] = 0;
3629 if (slide > 0) {
3630 tsetscroll(0, term->row-1);
3631 for (; slide > 0; --slide) tscrollup(0, 1, 1); // to fill history
3634 if (row < term->row) {
3635 /* free unneeded rows */
3636 for (int f = row; f < term->row; ++f) free(term->alt[f]);
3637 for (int f = term->linecount-(term->row-row); f < term->linecount; ++f) free(term->line[f]);
3638 term->linecount -= (term->row-row);
3639 /* resize to new height */
3640 term->alt = realloc(term->alt, row*sizeof(Line));
3641 term->line = realloc(term->line, term->linecount*sizeof(Line));
3642 } else if (row > term->row) {
3643 /* resize to new height */
3644 term->alt = realloc(term->alt, row*sizeof(Line));
3645 term->line = realloc(term->line, (row+term->maxhistory)*sizeof(Line));
3646 /* add more lines */
3647 for (int f = term->row; f < row; ++f) {
3648 term->alt[f] = calloc(col, sizeof(Glyph));
3649 for (int x = 0; x < col; ++x) term->alt[f][x] = g;
3651 for (int f = 0; f < row-term->row; ++f) {
3652 int y = term->linecount++;
3654 term->line[y] = calloc(col, sizeof(Glyph));
3655 for (int x = 0; x < col; ++x) term->line[y][x] = g;
3659 if (row != term->row) {
3660 term->dirty = realloc(term->dirty, row*sizeof(*term->dirty));
3663 /* resize each row to new width, zero-pad if needed */
3664 for (int f = 0; f < term->linecount; ++f) {
3665 term->line[f] = realloc(term->line[f], col*sizeof(Glyph));
3666 for (int x = mincol; x < col; ++x) term->line[f][x] = g;
3667 if (f < row) {
3668 markDirty(f, 2);
3669 term->alt[f] = realloc(term->alt[f], col*sizeof(Glyph));
3670 for (int x = mincol; x < col; ++x) term->alt[f][x] = g;
3673 /* update terminal size */
3674 term->topline = 0;
3675 term->col = col;
3676 term->row = row;
3677 /* make use of the LIMIT in tmoveto */
3678 tmoveto(term->c.x, term->c.y);
3679 /* reset scrolling region */
3680 tsetscroll(0, row-1);
3681 tfulldirt();
3682 return (slide > 0);
3686 static void xresize (int col, int row) {
3687 Pixmap newbuf;
3688 int oldw, oldh;
3690 if (term == NULL) return;
3691 oldw = term->picbufw;
3692 oldh = term->picbufh;
3693 term->picbufw = MAX(1, col*xw.cw);
3694 term->picbufh = MAX(1, row*xw.ch);
3695 newbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3696 XCopyArea(xw.dpy, term->picbuf, newbuf, dc.gc, 0, 0, term->picbufw, term->picbufh, 0, 0);
3697 XFreePixmap(xw.dpy, term->picbuf);
3698 XSetForeground(xw.dpy, dc.gc, getColor(term->defbg));
3699 if (term->picbufw > oldw) {
3700 XFillRectangle(xw.dpy, newbuf, dc.gc, oldw, 0, term->picbufw-oldw, MIN(term->picbufh, oldh));
3701 } else if (term->picbufw < oldw && xw.w > term->picbufw) {
3702 XClearArea(xw.dpy, xw.win, term->picbufw, 0, xw.w-term->picbufh, MIN(term->picbufh, oldh), False);
3704 if (term->picbufh > oldh) {
3705 XFillRectangle(xw.dpy, newbuf, dc.gc, 0, oldh, term->picbufw, term->picbufh-oldh);
3706 } else if (term->picbufh < oldh && xw.h > term->picbufh) {
3707 XClearArea(xw.dpy, xw.win, 0, term->picbufh, xw.w, xw.h-term->picbufh, False);
3709 term->picbuf = newbuf;
3710 tfulldirt();
3711 updateTabBar = 1;
3715 ////////////////////////////////////////////////////////////////////////////////
3716 // x11 drawing and utils
3718 static void xcreatebw (void) {
3719 if ((dc.bcol = calloc(MAX_COLOR+1, sizeof(dc.bcol[0]))) == NULL) die("out of memory");
3721 for (int f = 0; f <= MAX_COLOR; ++f) {
3722 XColor nclr;
3724 nclr = dc.ncol[f].pixel;
3725 XQueryColor(xw.dpy, xw.cmap, &nclr);
3726 fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", f, nclr.red, nclr.green, nclr.blue);
3732 static void xallocbwclr (int idx, XColor *color) {
3733 double lumi;
3735 XQueryColor(xw.dpy, xw.cmap, color);
3736 //fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", idx, color->red, color->green, color->blue);
3738 lumi = 0.3*((double)color->red/65535.0)+0.59*((double)color->green/65535.0)+0.11*((double)color->blue/65535.0);
3739 color->red = color->green = color->blue = (int)(lumi*65535.0);
3740 if (!XAllocColor(xw.dpy, xw.cmap, color)) {
3741 fprintf(stderr, "WARNING: could not allocate b/w color #%d\n", idx);
3742 return;
3744 dc.bcol[idx] = color->pixel;
3745 color->red = color->blue = 0;
3746 if (!XAllocColor(xw.dpy, xw.cmap, color)) {
3747 fprintf(stderr, "WARNING: could not allocate b/w color #%d\n", idx);
3748 return;
3750 dc.gcol[idx] = color->pixel;
3754 static void xallocnamedclr (int idx, const char *cname) {
3755 XColor color;
3757 if (!XAllocNamedColor(xw.dpy, xw.cmap, cname, &color, &color)) {
3758 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", idx, cname);
3759 return;
3761 dc.ncol[idx] = color.pixel;
3762 xallocbwclr(idx, &color);
3766 static void xloadcols (void) {
3767 int f, r, g, b;
3768 XColor color;
3769 ulong white = WhitePixel(xw.dpy, xw.scr);
3771 if ((dc.clrs[0] = dc.ncol = calloc(MAX_COLOR+1, sizeof(dc.ncol[0]))) == NULL) die("out of memory");
3772 if ((dc.clrs[1] = dc.bcol = calloc(MAX_COLOR+1, sizeof(dc.bcol[0]))) == NULL) die("out of memory");
3773 if ((dc.clrs[2] = dc.gcol = calloc(MAX_COLOR+1, sizeof(dc.gcol[0]))) == NULL) die("out of memory");
3775 for (f = 0; f <= MAX_COLOR; ++f) dc.ncol[f] = dc.bcol[f] = white;
3776 /* load colors [0-15] */
3777 for (f = 0; f <= 15; ++f) {
3778 const char *cname = opt_colornames[f]!=NULL?opt_colornames[f]:defcolornames[f];
3780 xallocnamedclr(f, cname);
3782 /* load colors [256-...] */
3783 for (f = 256; f <= MAX_COLOR; ++f) {
3784 const char *cname = opt_colornames[f];
3786 if (cname == NULL) {
3787 if (LEN(defextcolornames) <= f-256) continue;
3788 cname = defextcolornames[f-256];
3790 if (cname == NULL) continue;
3791 xallocnamedclr(f, cname);
3793 /* load colors [16-255] ; same colors as xterm */
3794 for (f = 16, r = 0; r < 6; ++r) {
3795 for (g = 0; g < 6; ++g) {
3796 for (b = 0; b < 6; ++b) {
3797 if (opt_colornames[f] != NULL) {
3798 xallocnamedclr(f, opt_colornames[f]);
3799 } else {
3800 color.red = r == 0 ? 0 : 0x3737+0x2828*r;
3801 color.green = g == 0 ? 0 : 0x3737+0x2828*g;
3802 color.blue = b == 0 ? 0 : 0x3737+0x2828*b;
3803 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3804 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3805 } else {
3806 dc.ncol[f] = color.pixel;
3807 xallocbwclr(f, &color);
3810 ++f;
3814 for (r = 0; r < 24; ++r, ++f) {
3815 if (opt_colornames[f] != NULL) {
3816 xallocnamedclr(f, opt_colornames[f]);
3817 } else {
3818 color.red = color.green = color.blue = 0x0808+0x0a0a*r;
3819 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3820 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3821 } else {
3822 dc.ncol[f] = color.pixel;
3823 xallocbwclr(f, &color);
3828 for (int f = 0; f < LEN(opt_colornames); ++f) if (opt_colornames[f]) free(opt_colornames[f]);
3832 static void xclear (int x1, int y1, int x2, int y2) {
3833 XSetForeground(xw.dpy, dc.gc, getColor(IS_SET(MODE_REVERSE) ? term->deffg : term->defbg));
3834 XFillRectangle(xw.dpy, term->picbuf, dc.gc, x1*xw.cw, y1*xw.ch, (x2-x1+1)*xw.cw, (y2-y1+1)*xw.ch);
3838 static void xhints (void) {
3839 XClassHint class = {opt_class, opt_title};
3840 XWMHints wm = {.flags = InputHint, .input = 1};
3841 XSizeHints size = {
3842 .flags = PSize | PResizeInc | PBaseSize,
3843 .height = xw.h,
3844 .width = xw.w,
3845 .height_inc = xw.ch,
3846 .width_inc = xw.cw,
3847 .base_height = xw.h/*xw.tabheight*/,
3848 .base_width = xw.w,
3850 //XSetWMNormalHints(xw.dpy, xw.win, &size);
3851 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, &size, &wm, &class);
3852 XSetWMProtocols(xw.dpy, xw.win, &XA_WM_DELETE_WINDOW, 1);
3856 static XFontSet xinitfont (const char *fontstr) {
3857 XFontSet set;
3858 char *def, **missing;
3859 int n;
3861 missing = NULL;
3862 set = XCreateFontSet(xw.dpy, fontstr, &missing, &n, &def);
3863 if (missing) {
3864 while (n--) fprintf(stderr, "sterm: missing fontset: %s\n", missing[n]);
3865 XFreeStringList(missing);
3867 return set;
3871 static void xgetfontinfo (XFontSet set, int *ascent, int *descent, short *lbearing, short *rbearing, Font *fid) {
3872 XFontStruct **xfonts;
3873 char **font_names;
3874 int n;
3876 *ascent = *descent = *lbearing = *rbearing = 0;
3877 n = XFontsOfFontSet(set, &xfonts, &font_names);
3878 for (int f = 0; f < n; ++f) {
3879 if (f == 0) *fid = (*xfonts)->fid;
3880 *ascent = MAX(*ascent, (*xfonts)->ascent);
3881 *descent = MAX(*descent, (*xfonts)->descent);
3882 *lbearing = MAX(*lbearing, (*xfonts)->min_bounds.lbearing);
3883 *rbearing = MAX(*rbearing, (*xfonts)->max_bounds.rbearing);
3884 ++xfonts;
3889 static void initfonts (const char *fontstr, const char *bfontstr, const char *tabfont) {
3890 if ((dc.font[0].set = xinitfont(fontstr)) == NULL) {
3891 if ((dc.font[0].set = xinitfont(FONT)) == NULL) die("can't load font %s", fontstr);
3893 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);
3895 if ((dc.font[1].set = xinitfont(bfontstr)) == NULL) {
3896 if ((dc.font[1].set = xinitfont(FONTBOLD)) == NULL) die("can't load font %s", bfontstr);
3898 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);
3900 if ((dc.font[2].set = xinitfont(tabfont)) == NULL) {
3901 if ((dc.font[2].set = xinitfont(FONTTAB)) == NULL) die("can't load font %s", tabfont);
3903 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);
3907 static void xinit (void) {
3908 XSetWindowAttributes attrs;
3909 Window parent;
3910 XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
3912 if (!(xw.dpy = XOpenDisplay(NULL))) die("can't open display");
3914 XA_VT_SELECTION = XInternAtom(xw.dpy, "_STERM_SELECTION_", 0);
3915 XA_CLIPBOARD = XInternAtom(xw.dpy, "CLIPBOARD", 0);
3916 XA_UTF8 = XInternAtom(xw.dpy, "UTF8_STRING", 0);
3917 XA_NETWM_NAME = XInternAtom(xw.dpy, "_NET_WM_NAME", 0);
3918 XA_TARGETS = XInternAtom(xw.dpy, "TARGETS", 0);
3919 XA_WM_DELETE_WINDOW = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", 0);
3920 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
3922 xw.scr = XDefaultScreen(xw.dpy);
3923 /* font */
3924 initfonts(opt_fontnorm, opt_fontbold, opt_fonttab);
3925 /* XXX: Assuming same size for bold font */
3926 xw.cw = dc.font[0].rbearing-dc.font[0].lbearing;
3927 xw.ch = dc.font[0].ascent+dc.font[0].descent;
3928 xw.tch = dc.font[2].ascent+dc.font[2].descent;
3929 xw.tabheight = opt_disabletabs ? 0 : xw.tch+2;
3930 //fprintf(stderr, "tabh: %d\n", xw.tabheight);
3931 //xw.tabheight = 0;
3932 /* colors */
3933 xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
3934 xloadcols();
3935 /* window - default size */
3936 term->picbufh = term->row*xw.ch;
3937 term->picbufw = term->col*xw.cw;
3939 xw.h = term->picbufh+xw.tabheight;
3940 xw.w = term->picbufw;
3942 attrs.background_pixel = getColor(defaultBG);
3943 attrs.border_pixel = getColor(defaultBG);
3944 attrs.bit_gravity = NorthWestGravity;
3945 attrs.event_mask = FocusChangeMask | KeyPressMask
3946 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
3947 | /*ButtonMotionMask*/ PointerMotionMask | ButtonPressMask | ButtonReleaseMask
3948 | EnterWindowMask | LeaveWindowMask;
3949 attrs.colormap = xw.cmap;
3950 //fprintf(stderr, "oe: [%s]\n", opt_embed);
3951 parent = opt_embed ? strtol(opt_embed, NULL, 0) : XRootWindow(xw.dpy, xw.scr);
3952 xw.win = XCreateWindow(xw.dpy, parent, 0, 0,
3953 xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
3954 XDefaultVisual(xw.dpy, xw.scr),
3955 CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask
3956 | CWColormap,
3957 &attrs);
3958 xhints();
3959 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3960 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
3961 /* input methods */
3962 if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) die("XOpenIM() failed");
3963 xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL);
3964 /* gc */
3965 dc.gc = XCreateGC(xw.dpy, xw.win, 0, NULL);
3966 /* white cursor, black outline */
3967 xw.cursor = XCreateFontCursor(xw.dpy, XC_xterm);
3968 XDefineCursor(xw.dpy, xw.win, xw.cursor);
3969 XRecolorCursor(xw.dpy, xw.cursor,
3970 &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff},
3971 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
3972 fixWindowTitle(term);
3973 //XStoreName(xw.dpy, xw.win, opt_title);
3975 XSetForeground(xw.dpy, dc.gc, 0);
3976 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
3977 if (xw.tabheight > 0) XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
3979 XMapWindow(xw.dpy, xw.win);
3981 #if BLANKPTR_USE_GLYPH_CURSOR
3982 xw.blankPtr = XCreateGlyphCursor(xw.dpy, dc.font[0].fid, dc.font[0].fid, ' ', ' ', &blackcolor, &blackcolor);
3983 #else
3984 static const char cmbmp[1] = {0};
3985 Pixmap pm;
3987 pm = XCreateBitmapFromData(xw.dpy, xw.win, cmbmp, 1, 1);
3988 xw.blankPtr = XCreatePixmapCursor(xw.dpy, pm, pm, &blackcolor, &blackcolor, 0, 0);
3989 XFreePixmap(xw.dpy, pm);
3990 #endif
3992 XSync(xw.dpy, 0);
3996 static void xblankPointer (void) {
3997 if (!ptrBlanked && xw.blankPtr != None) {
3998 ptrBlanked = 1;
3999 XDefineCursor(xw.dpy, xw.win, xw.blankPtr);
4000 XFlush(xw.dpy);
4005 static void xunblankPointer (void) {
4006 if (ptrBlanked && xw.cursor != None) {
4007 ptrBlanked = 0;
4008 XDefineCursor(xw.dpy, xw.win, xw.cursor);
4009 XFlush(xw.dpy);
4010 ptrLastMove = mclock_ticks();
4015 static void xdraws (const char *s, const Glyph *base, int x, int y, int charlen, int bytelen) {
4016 int fg = base->fg, bg = base->bg, temp;
4017 int winx = x*xw.cw, winy = y*xw.ch+dc.font[0].ascent, width = charlen*xw.cw;
4018 XFontSet fontset = dc.font[0].set;
4019 int defF = base->attr&ATTR_DEFFG, defB = base->attr&ATTR_DEFBG;
4021 /* only switch default fg/bg if term is in RV mode */
4022 if (IS_SET(MODE_REVERSE)) {
4023 if (defF) fg = term->defbg;
4024 if (defB) bg = term->deffg;
4026 if (base->attr&ATTR_REVERSE) defF = defB = 0;
4027 if (base->attr&ATTR_BOLD) {
4028 if (defF && defB && defaultBoldFG >= 0) fg = defaultBoldFG;
4029 else if (fg < 8) fg += 8;
4030 fontset = dc.font[1].set;
4032 if ((base->attr&ATTR_UNDERLINE) && defaultUnderlineFG >= 0) {
4033 if (defF && defB) fg = defaultUnderlineFG;
4036 if (base->attr&ATTR_REVERSE) { temp = fg; fg = bg; bg = temp; }
4038 XSetBackground(xw.dpy, dc.gc, getColor(bg));
4039 XSetForeground(xw.dpy, dc.gc, getColor(fg));
4043 FILE *fo = fopen("zlog.log", "ab");
4044 fprintf(fo, "xdraws: x=%d; y=%d; charlen=%d; bytelen=%d\n", x, y, charlen, bytelen);
4045 fclose(fo);
4049 if (IS_GFX(base->attr)) {
4050 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
4051 } else if (!needConversion) {
4052 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
4053 } else {
4054 if (bytelen > 0) {
4055 //k8: dunno why, but Xutf8DrawImageString() ignores ascii (at least for terminus)
4056 const char *pos = s;
4057 int xpos = winx;
4059 while (pos < s+bytelen) {
4060 const char *e;
4061 int clen;
4063 if ((unsigned char)(pos[0]) < 128) {
4064 for (e = pos+1; e < s+bytelen && (unsigned char)(*e) < 128; ++e) ;
4065 clen = e-pos;
4066 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
4069 FILE *fo = fopen("zlog.log", "ab");
4070 fprintf(fo, " norm: clen=%d (%d); [", clen, (int)(e-pos));
4071 fwrite(pos, 1, e-pos, fo);
4072 fprintf(fo, "]\n");
4073 fclose(fo);
4076 } else {
4077 for (clen = 0, e = pos; e < s+bytelen && (unsigned char)(*e) >= 128; ++e) {
4078 if (((unsigned char)(e[0])&0xc0) == 0xc0) ++clen;
4080 Xutf8DrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
4083 FILE *fo = fopen("zlog.log", "ab");
4084 fprintf(fo, " utf8: clen=%d (%d); [", clen, (int)(e-pos));
4085 fwrite(pos, 1, e-pos, fo);
4086 fprintf(fo, "]\n");
4087 fclose(fo);
4091 xpos += xw.cw*clen;
4092 pos = e;
4097 if (opt_drawunderline && (base->attr&ATTR_UNDERLINE)) {
4098 XDrawLine(xw.dpy, term->picbuf, dc.gc, winx, winy+1, winx+width-1, winy+1);
4103 /* copy buffer pixmap to screen pixmap */
4104 static void xcopy (int x, int y, int cols, int rows) {
4105 int src_x = x*xw.cw, src_y = y*xw.ch, src_w = cols*xw.cw, src_h = rows*xw.ch;
4106 int dst_x = src_x, dst_y = src_y;
4108 if (opt_tabposition == 1) { dst_y += xw.tabheight; }
4109 XCopyArea(xw.dpy, term->picbuf, xw.win, dc.gc, src_x, src_y, src_w, src_h, dst_x, dst_y);
4113 static void xdrawcursor (void) {
4114 Glyph g;
4115 int sl, scrx, scry, cmy;
4117 if (term == NULL) return;
4119 LIMIT(term->oldcx, 0, term->col-1);
4120 LIMIT(term->oldcy, 0, term->row-1);
4122 cmy = term->row-term->topline-1;
4124 if (!(xw.state&WIN_FOCUSED) && !term->curblinkinactive) term->curbhidden = 0;
4126 if (term->cmdMode == CMDMODE_NONE || term->oldcy != cmy) {
4127 scrx = term->oldcx;
4128 scry = term->oldcy+term->topline;
4129 if (scry >= 0 && scry < term->row) {
4130 if (term->curbhidden < 0 ||
4131 term->oldcy != term->c.y || term->oldcx != term->c.x ||
4132 (term->c.state&CURSOR_HIDE) ||
4133 !(xw.state&WIN_FOCUSED)) {
4134 /* remove the old cursor */
4135 sl = utf8size(term->line[term->oldcy][scrx].c);
4136 g = term->line[term->oldcy][scrx];
4137 if (selected(scrx, term->c.y)) g.attr ^= ATTR_REVERSE;
4138 xdraws(g.c, &g, scrx, scry, 1, sl);
4139 //xclear(scrx, term->oldcy, scrx, term->oldcy);
4140 xcopy(scrx, scry, 1, 1);
4142 if (term->curbhidden) term->curbhidden = 1;
4146 if (term->cmdMode != CMDMODE_NONE && term->oldcy == cmy) return;
4147 if ((term->c.state&CURSOR_HIDE) != 0) return;
4148 if (term->curbhidden) return;
4149 /* draw the new one */
4150 scrx = term->c.x;
4151 scry = term->c.y+term->topline;
4152 if (scry >= 0 && scry < term->row) {
4153 int nodraw = 0;
4155 if (!(xw.state&WIN_FOCUSED)) {
4156 if (defaultCursorInactiveBG < 0) {
4157 XSetForeground(xw.dpy, dc.gc, getColor(defaultCursorBG));
4158 XDrawRectangle(xw.dpy, term->picbuf, dc.gc, scrx*xw.cw, scry*xw.ch, xw.cw-1, xw.ch-1);
4159 nodraw = 1;
4160 } else {
4161 g.bg = defaultCursorInactiveBG;
4162 g.fg = defaultCursorInactiveFG;
4164 } else {
4165 g.fg = defaultCursorFG;
4166 g.bg = defaultCursorBG;
4168 if (!nodraw) {
4169 memcpy(g.c, term->line[term->c.y][scrx].c, UTF_SIZ);
4170 g.state = 0;
4171 g.attr = ATTR_NULL;
4172 if (IS_SET(MODE_REVERSE)) g.attr |= ATTR_REVERSE;
4173 sl = utf8size(g.c);
4174 xdraws(g.c, &g, scrx, scry, 1, sl);
4176 term->oldcx = scrx;
4177 term->oldcy = term->c.y;
4178 xcopy(scrx, scry, 1, 1);
4183 static void xdrawTabBar (void) {
4184 if (xw.tabheight > 0 && updateTabBar) {
4185 if (updateTabBar > 0) {
4186 int tabw = xw.w/opt_tabcount;
4187 XFontSet fontset = dc.font[2].set;
4189 XSetForeground(xw.dpy, dc.gc, getColor(normalTabBG));
4190 XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
4192 for (int f = firstVisibleTab; f < firstVisibleTab+opt_tabcount; ++f) {
4193 int x = (f-firstVisibleTab)*tabw;
4194 const char *title;
4195 char *tit;
4197 if (f >= term_count) {
4198 XSetForeground(xw.dpy, dc.gc, getColor(normalTabBG));
4199 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, xw.w, xw.tabheight);
4201 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
4202 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
4203 break;
4205 title = term_array[f]->title;
4206 if (!title[0]) title = opt_title;
4207 tit = SPrintf("[%d]%s", f, title);
4208 title = tit;
4210 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
4211 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, tabw, xw.tabheight);
4213 XSetBackground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
4214 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabFG : normalTabFG));
4216 if (needConversion) {
4217 int xx = x+2;
4219 while (*title && xx < x+tabw) {
4220 const char *e = title;
4221 XRectangle r;
4223 memset(&r, 0, sizeof(r));
4225 if ((unsigned char)(*e) > 127) {
4226 while (*e && (unsigned char)(*e) > 127) ++e;
4227 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
4228 Xutf8TextExtents(fontset, title, e-title, &r, NULL);
4229 } else {
4230 while (*e && (unsigned char)(*e) <= 127) ++e;
4231 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
4232 XmbTextExtents(fontset, title, e-title, &r, NULL);
4234 title = e;
4235 xx += r.width-r.x;
4238 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
4239 } else {
4240 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
4242 free(tit);
4244 XSetForeground(xw.dpy, dc.gc, getColor(f == termidx ? activeTabBG : normalTabBG));
4245 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x+tabw-2, 0, 2, xw.tabheight);
4247 if (f > firstVisibleTab) {
4248 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
4249 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
4253 XSetForeground(xw.dpy, dc.gc, getColor(normalTabFG));
4254 //XDrawRectangle(xw.dpy, xw.pictab, dc.gc, -1, 0, xw.w+1, xw.tabheight+1);
4255 if (opt_tabposition == 0) {
4256 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, 0);
4257 } else {
4258 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, xw.tabheight-1, xw.w, xw.tabheight-1);
4262 if (opt_tabposition == 0) {
4263 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, xw.h-xw.tabheight);
4264 } else {
4265 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, 0);
4268 updateTabBar = 0;
4272 static void drawcmdline (int scry) {
4273 Glyph base;
4274 int cpos = term->cmdofs, bc = 0, x, sx;
4275 int back = (term->cmdMode == CMDMODE_INPUT ? 21 : 124);
4277 base.attr = ATTR_NULL;
4278 base.fg = 255;
4279 base.bg = back;
4280 base.state = 0;
4281 for (sx = x = 0; x < term->col && term->cmdline[cpos]; ++x) {
4282 int l = utf8size(term->cmdline+cpos);
4284 if (bc+l > DRAW_BUF_SIZ) {
4285 xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
4286 bc = 0;
4287 sx = x;
4289 memcpy(term->drawbuf+bc, term->cmdline+cpos, l);
4290 cpos += l;
4291 bc += l;
4293 if (bc > 0) xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
4295 if (x < term->col && term->cmdMode == CMDMODE_INPUT) {
4296 base.fg = back;
4297 base.bg = 255;
4298 xdraws(" ", &base, x, scry, 1, 1);
4299 ++x;
4302 if (x < term->col) {
4303 base.fg = 255;
4304 base.bg = back;
4305 memset(term->drawbuf, ' ', DRAW_BUF_SIZ);
4306 while (x < term->col) {
4307 sx = x;
4308 x += DRAW_BUF_SIZ;
4309 if (x > term->col) x = term->col;
4310 xdraws(term->drawbuf, &base, sx, scry, x-sx, x-sx);
4314 xcopy(0, scry, term->col, 1);
4318 static void drawline (int x1, int x2, int scry, int lineno, int dontcopy) {
4319 //fprintf(stderr, "%d: drawline: x1=%d; x2=%d; scry=%d; row:%d; lineno=%d\n", mclock_ticks(), x1, x2, scry, term->row, lineno);
4320 if (scry < 0 || scry >= term->row) return;
4321 if (scry == term->row-1 && term->cmdMode != CMDMODE_NONE) { drawcmdline(scry); return; }
4322 if (lineno < 0 || lineno >= term->linecount) {
4323 xclear(0, scry, term->col-1, scry);
4324 xcopy(0, scry, term->col, 1);
4325 } else {
4326 int ic, ib, ox, sl;
4327 int stx, ex;
4328 Glyph base, new;
4329 Line l = term->line[lineno];
4331 if (lineno < term->row && term->topline == 0) {
4332 if (!term->dirty[lineno]) return;
4333 if (!dontcopy) {
4334 // fix 'dirty' flag for line
4335 if (term->dirty[lineno]&0x02) {
4336 // mark full line as dirty
4337 stx = 0;
4338 ex = term->col;
4339 term->dirty[lineno] = 0;
4340 } else {
4341 term->dirty[lineno] = 0;
4342 if (x1 > 0) for (int x = 0; x < x1; ++x) if (l[x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
4343 if (!term->dirty[lineno] && x2 < term->col) for (int x = x2; x < term->col; ++x) if (l[x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
4345 // find dirty region
4346 for (stx = x1; stx < x2; ++stx) if (l[stx].state&GLYPH_DIRTY) break;
4347 for (ex = x2; ex > stx; --ex) if (l[ex-1].state&GLYPH_DIRTY) break;
4348 if (stx >= x2 || ex <= stx) return; // nothing to do
4350 } else {
4351 // 'dontcopy' means that the whole screen is dirty
4352 stx = 0;
4353 ex = term->col;
4354 term->dirty[lineno] = 0;
4356 } else {
4357 //if (lineno < term->row) term->dirty[lineno] = 0;
4358 stx = 0;
4359 ex = term->col;
4362 base = l[stx];
4363 if (term->sel.bx != -1 && selected(stx, lineno)) base.attr ^= ATTR_REVERSE;
4364 ic = ib = 0;
4365 ox = stx;
4366 //xclear(stx, scry, ex-1, scry); //k8: actually, we don't need this for good monospace fonts, so...
4367 for (int x = stx; x < ex; ++x) {
4368 new = l[x];
4369 l[x].state &= ~GLYPH_DIRTY; //!
4370 if (term->sel.bx != -1 && selected(x, lineno)) new.attr ^= ATTR_REVERSE;
4371 if (ib > 0 && (ATTRCMP(base, new) || ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
4372 // flush draw buffer
4373 xdraws(term->drawbuf, &base, ox, scry, ic, ib);
4374 ic = ib = 0;
4376 if (ib == 0) { ox = x; base = new; }
4377 sl = utf8size(new.c);
4378 memcpy(term->drawbuf+ib, new.c, sl);
4379 ib += sl;
4380 ++ic;
4382 if (ib > 0) xdraws(term->drawbuf, &base, ox, scry, ic, ib);
4383 //xcopy(0, scry, term->col, 1);
4384 //if (term->c.y == lineno && term->c.x >= stx && term->c.x < ex) xdrawcursor();
4385 if (!dontcopy) xcopy(stx, scry, ex-stx, 1);
4390 static void drawregion (int x1, int y1, int x2, int y2, int forced) {
4391 int fulldirty = 0;
4393 if (!forced && (xw.state&WIN_VISIBLE) == 0) {
4394 //dlogf("invisible");
4395 lastDrawTime = term->lastDrawTime = 1;
4396 term->wantRedraw = 1;
4397 return;
4400 if (y1 == 0 && y2 == term->row) {
4401 fulldirty = 1;
4402 for (int y = 0; y < y2; ++y) if (!(term->dirty[y]&0x02)) { fulldirty = 0; break; }
4405 if (term->topline < term->row) {
4406 for (int y = y1; y < y2; ++y) drawline(x1, x2, y+term->topline, y, fulldirty);
4408 if (term->topline > 0) {
4409 int scry = MIN(term->topline, term->row), y = term->row;
4411 fulldirty = 1;
4412 if (term->topline >= term->row) y += term->topline-term->row;
4413 while (--scry >= 0) {
4414 drawline(0, term->col, scry, y, 0);
4415 ++y;
4418 if (fulldirty) xcopy(0, 0, term->col, term->row);
4419 xdrawcursor();
4420 xdrawTabBar();
4421 //XFlush(xw.dpy);
4422 lastDrawTime = term->lastDrawTime = mclock_ticks();
4423 term->wantRedraw = 0;
4427 static void draw (int forced) {
4428 if (term != NULL) {
4429 //fprintf(stderr, "draw(%d) (%d)\n", forced, mclock_ticks());
4430 drawregion(0, 0, term->col, term->row, forced);
4435 static void expose (XEvent *ev) {
4436 XExposeEvent *e = &ev->xexpose;
4438 if (xw.state&WIN_REDRAW) {
4439 if (!e->count && term != NULL) {
4440 xw.state &= ~WIN_REDRAW;
4441 xcopy(0, 0, term->col, term->row);
4442 updateTabBar = -1;
4444 } else if (term != NULL) {
4445 int taby = (opt_tabposition==1 ? 0 : xw.h-xw.tabheight);
4446 int termy = (opt_tabposition==1 ? xw.tabheight : 0);
4447 int x0 = e->x/xw.cw, y0 = (e->y-termy)/xw.ch;
4448 int x1 = (e->x+e->width)/xw.cw, y1 = (e->y-termy+e->height)/xw.ch;
4450 //fprintf(stderr, "x=%d; y=%d; w=%d; h=%d\n", e->x, e->y, e->width, e->height);
4451 //fprintf(stderr, "taby=%d; tabh=%d\n", taby, xw.tabheight);
4452 //fprintf(stderr, "x0=%d; y0=%d; x1=%d; y1=%d\n", x0, y0, x1, y1);
4454 if (e->y <= taby+xw.tabheight && e->y+e->height >= taby) {
4455 //fprintf(stderr, "tabbar!\n");
4456 updateTabBar = -1;
4458 //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)));
4459 //xcopy(0, 0, term->col, term->row);
4460 //xclear(x0, y0, x1-x0+1, y1-y0+1);
4461 LIMIT(x0, 0, term->col-1);
4462 LIMIT(x1, 0, term->col-1);
4463 LIMIT(y0, 0, term->row-1);
4464 LIMIT(y1, 0, term->row-1);
4465 //fprintf(stderr, "*:x0=%d; y0=%d; x1=%d; y1=%d\n", x0, y0, x1, y1);
4466 xcopy(x0, y0, x1-x0+1, y1-y0+1);
4467 } else {
4468 updateTabBar = -1;
4470 xdrawTabBar();
4471 //XFlush(xw.dpy);
4475 static void visibility (XEvent *ev) {
4476 XVisibilityEvent *e = &ev->xvisibility;
4478 if (e->state == VisibilityFullyObscured) xw.state &= ~WIN_VISIBLE;
4479 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 */
4483 static void unmap (XEvent *ev) {
4484 xw.state &= ~WIN_VISIBLE;
4488 static void xseturgency (int add) {
4489 XWMHints *h = XGetWMHints(xw.dpy, xw.win);
4491 h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
4492 XSetWMHints(xw.dpy, xw.win, h);
4493 XFree(h);
4497 static void focus (XEvent *ev) {
4498 if (ev->type == FocusIn) {
4499 xw.state |= WIN_FOCUSED;
4500 xseturgency(0);
4501 tsendfocusevent(1);
4502 } else {
4503 xw.state &= ~WIN_FOCUSED;
4504 tsendfocusevent(0);
4506 //draw(1);
4507 updateTabBar = -1;
4508 xdrawTabBar();
4509 xdrawcursor();
4510 //xcopy(0, 0, term->col, term->row);
4514 ////////////////////////////////////////////////////////////////////////////////
4515 // keyboard mapping
4516 static const char *kmap (KeySym k, uint state) {
4517 const char *res = NULL;
4519 state &= ~Mod2Mask; // numlock
4520 for (int f = 0; f < keymap_used; ++f) {
4521 uint mask = keymap[f].mask;
4523 if (keymap[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) {
4524 //fprintf(stderr, "kp: %d\n", IS_SET(MODE_APPKEYPAD));
4525 if (!IS_SET(MODE_APPKEYPAD)) {
4526 if (!keymap[f].kp) {
4527 //fprintf(stderr, "nokp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4528 return keymap[f].str; // non-keypad hit
4530 continue;
4532 //fprintf(stderr, "kp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4533 if (keymap[f].kp) return keymap[f].str; // keypad hit
4534 res = keymap[f].str; // kp mode, but non-kp mapping found
4537 return res;
4541 static const char *kbind (KeySym k, uint state) {
4542 state &= ~Mod2Mask; // numlock
4543 for (int f = 0; f < keybinds_used; ++f) {
4544 uint mask = keybinds[f].mask;
4546 if (keybinds[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) return keybinds[f].str;
4548 return NULL;
4552 static KeySym do_keytrans (KeySym ks) {
4553 for (int f = 0; f < keytrans_used; ++f) if (keytrans[f].src == ks) return keytrans[f].dst;
4554 return ks;
4558 static void cmdline_closequeryexec (int cancelled) {
4559 if (!cancelled) {
4560 char *rep = term->cmdline+term->cmdreslen;
4562 trimstr(rep);
4563 if (!rep[0] || strchr("yt", tolower(rep[0])) != NULL) {
4564 closeRequestComes = 2;
4565 return;
4568 closeRequestComes = 0;
4572 static void kpress (XEvent *ev) {
4573 XKeyEvent *e = &ev->xkey;
4574 KeySym ksym = NoSymbol;
4575 const char *kstr;
4576 int len;
4577 Status status;
4578 char buf[32];
4580 if (term == NULL) return;
4582 if (!ptrBlanked && opt_ptrblank > 0) xblankPointer();
4584 //if (len < 1) len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
4585 if ((len = Xutf8LookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status)) > 0) buf[len] = 0;
4586 // leave only known mods
4587 e->state &= (Mod1Mask | Mod4Mask | ControlMask | ShiftMask);
4588 #ifdef DUMP_KEYSYMS
4590 const char *ksname = XKeysymToString(ksym);
4592 fprintf(stderr, "utf(%d):[%s] (%s) 0x%08x\n", len, len>=0?buf:"<shit>", ksname, (unsigned int)e->state);
4594 #endif
4595 if ((kstr = kbind(ksym, e->state)) != NULL) {
4596 // keybind found
4597 executeCommands(kstr);
4598 return;
4601 if (term->cmdMode != CMDMODE_NONE) {
4602 int mode = term->cmdMode;
4604 switch (do_keytrans(ksym)) {
4605 case XK_Return:
4606 tcmdlinehide();
4607 if (term->cmdexecfn) term->cmdexecfn(0);
4608 else if (mode == CMDMODE_INPUT) executeCommands(term->cmdline);
4609 break;
4610 case XK_BackSpace:
4611 if (mode == CMDMODE_INPUT) {
4612 tcmdlinechoplast();
4613 term->cmdtabpos = -1;
4614 term->cmdprevc = NULL;
4615 } else {
4616 tcmdlinehide();
4617 if (term->cmdexecfn) term->cmdexecfn(1);
4619 break;
4620 case XK_Escape:
4621 tcmdlinehide();
4622 if (term->cmdexecfn) term->cmdexecfn(1);
4623 break;
4624 case XK_Tab:
4625 if (mode == CMDMODE_INPUT && term->cmdline[0] && term->cmdcl == 0 && term->cmdexecfn == NULL) {
4626 const char *cpl;
4628 if (term->cmdtabpos < 0) {
4629 term->cmdtabpos = 0;
4630 while (term->cmdline[term->cmdtabpos] && isalnum(term->cmdline[term->cmdtabpos])) ++term->cmdtabpos;
4631 if (term->cmdline[term->cmdtabpos]) {
4632 term->cmdtabpos = -1;
4633 break;
4635 term->cmdprevc = NULL;
4637 cpl = findCommandCompletion(term->cmdline, term->cmdtabpos, term->cmdprevc);
4638 if (cpl == NULL && term->cmdprevc != NULL) cpl = findCommandCompletion(term->cmdline, term->cmdtabpos, NULL);
4639 term->cmdprevc = cpl;
4640 if (cpl != NULL) strcpy(term->cmdline, cpl);
4641 tcmdlinefixofs();
4642 } else if (mode != CMDMODE_INPUT) {
4643 tcmdlinehide();
4644 if (term->cmdexecfn) term->cmdexecfn(1);
4646 break;
4647 default:
4648 if (mode == CMDMODE_INPUT) {
4649 if (len > 0 && (unsigned char)buf[0] >= 32) {
4650 tcmdput(buf, len);
4651 term->cmdtabpos = -1;
4652 term->cmdprevc = NULL;
4655 break;
4657 return;
4660 if ((kstr = kmap(do_keytrans(ksym), e->state)) != NULL) {
4661 if (kstr[0]) {
4662 tunshowhistory();
4663 ttywritestr(kstr);
4665 } else {
4666 int meta = (e->state&Mod1Mask);
4668 int shift = (e->state&ShiftMask);
4669 int ctrl = (e->state&ControlMask);
4671 switch (ksym) {
4672 case XK_Return:
4673 tunshowhistory();
4674 if (meta) {
4675 ttywritestr("\x1b\x0a");
4676 } else {
4677 if (IS_SET(MODE_CRLF)) ttywritestr("\r\n"); else ttywritestr("\r");
4679 break;
4680 default:
4681 if (len > 0) {
4682 tunshowhistory();
4683 if (meta && len == 1) ttywritestr("\x1b");
4684 ttywrite(buf, len);
4686 break;
4692 ////////////////////////////////////////////////////////////////////////////////
4693 // xembed?
4694 static void cmessage (XEvent *e) {
4695 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
4696 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
4697 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
4698 xw.state |= WIN_FOCUSED;
4699 xseturgency(0);
4700 tsendfocusevent(1);
4701 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
4702 xw.state &= ~WIN_FOCUSED;
4703 tsendfocusevent(0);
4705 xdrawcursor();
4706 xdrawTabBar();
4707 xcopy(0, 0, term->col, term->row);
4708 return;
4711 if (e->xclient.data.l[0] == XA_WM_DELETE_WINDOW) {
4712 closeRequestComes = 1;
4713 return;
4718 ////////////////////////////////////////////////////////////////////////////////
4719 static void resize (XEvent *e) {
4720 int col, row;
4721 Term *ot = term;
4723 //if (e->xconfigure.width == 65535 || e->xconfigure.width == -1) e->xconfigure.width = xw.w;
4724 if (e->xconfigure.height == 65535 || e->xconfigure.height == -1) e->xconfigure.height = xw.h;
4725 //if ((short int)e->xconfigure.height < xw.ch) return;
4727 if (e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) return;
4728 xw.w = e->xconfigure.width;
4729 xw.h = e->xconfigure.height;
4730 col = xw.w/xw.cw;
4731 row = (xw.h-xw.tabheight)/xw.ch;
4732 //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);
4733 if (col == term->col && row == term->row) return;
4734 for (int f = 0; f < term_count; ++f) {
4735 term = term_array[f];
4736 if (tresize(col, row) && ot == term) draw(1);
4737 ttyresize();
4738 xresize(col, row);
4740 term = ot;
4741 XFreePixmap(xw.dpy, xw.pictab);
4742 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
4743 updateTabBar = 1;
4747 static inline int last_draw_too_old (void) {
4748 int tt = mclock_ticks();
4750 if (term != NULL) {
4751 if (term->dead || !term->wantRedraw) return 0;
4752 return (tt-term->lastDrawTime >= opt_drawtimeout);
4754 return (tt-lastDrawTime >= opt_maxdrawtimeout);
4758 ////////////////////////////////////////////////////////////////////////////////
4759 // main loop
4760 static void (*handler[LASTEvent])(XEvent *) = {
4761 [KeyPress] = kpress,
4762 [ClientMessage] = cmessage,
4763 [ConfigureNotify] = resize,
4764 [VisibilityNotify] = visibility,
4765 [UnmapNotify] = unmap,
4766 [Expose] = expose,
4767 [FocusIn] = focus,
4768 [FocusOut] = focus,
4769 [MotionNotify] = bmotion,
4770 [ButtonPress] = bpress,
4771 [ButtonRelease] = brelease,
4772 [SelectionNotify] = selnotify,
4773 [SelectionRequest] = selrequest,
4774 [SelectionClear] = selclear,
4778 static void run (void) {
4779 //int stuff_to_print = 0;
4780 int xfd = XConnectionNumber(xw.dpy);
4782 ptrLastMove = mclock_ticks();
4783 while (term_count > 0) {
4784 XEvent ev;
4785 fd_set rfd, wfd;
4786 struct timeval timeout;
4787 int maxfd = xfd;
4788 Term *ot;
4789 int dodraw;
4791 FD_ZERO(&rfd);
4792 FD_ZERO(&wfd);
4793 FD_SET(xfd, &rfd);
4794 //FD_SET(term->cmdfd, &rfd);
4795 // have something to write?
4796 for (int f = 0; f < term_count; ++f) {
4797 Term *t = term_array[f];
4799 if (!t->dead && term->cmdfd >= 0 && t->pid != 0) {
4800 if (t->cmdfd > maxfd) maxfd = t->cmdfd;
4801 FD_SET(t->cmdfd, &rfd);
4802 if (t->wrbufpos < t->wrbufused) FD_SET(t->cmdfd, &wfd);
4806 timeout.tv_sec = 0;
4807 timeout.tv_usec = (opt_drawtimeout+2)*1000;
4808 if (select(maxfd+1, &rfd, &wfd, NULL, &timeout) < 0) {
4809 if (errno == EINTR) continue;
4810 die("select failed: %s", SERRNO);
4813 ot = term;
4814 for (int f = 0; f < term_count; ++f) {
4815 Term *t = term_array[f];
4817 if (!t->dead && term->cmdfd >= 0 && term->pid != 0) {
4818 term = t;
4819 if (FD_ISSET(t->cmdfd, &wfd)) ttyflushwrbuf();
4820 if (FD_ISSET(t->cmdfd, &rfd)) ttyread(); //t->wantRedraw = 1;
4821 term = ot;
4825 termcleanup();
4826 if (term_count == 0) exit(exitcode);
4828 dodraw = 0;
4829 if (term != NULL && term->curblink > 0) {
4830 int tt = mclock_ticks();
4832 if (tt-term->lastBlinkTime >= term->curblink) {
4833 term->lastBlinkTime = tt;
4834 if ((xw.state&WIN_FOCUSED) || term->curblinkinactive) {
4835 term->curbhidden = (term->curbhidden ? 0 : -1);
4836 dodraw = 1;
4837 } else {
4838 term->curbhidden = 0;
4842 if (updateTabBar) xdrawTabBar();
4843 if (dodraw || last_draw_too_old()) draw(0);
4845 if (XPending(xw.dpy)) {
4846 while (XPending(xw.dpy)) {
4847 XNextEvent(xw.dpy, &ev);
4848 switch (ev.type) {
4849 //case VisibilityNotify:
4850 //case UnmapNotify:
4851 //case FocusIn:
4852 //case FocusOut:
4853 case MotionNotify:
4854 case ButtonPress:
4855 case ButtonRelease:
4856 xunblankPointer();
4857 ptrLastMove = mclock_ticks();
4858 break;
4859 default: ;
4861 if (XFilterEvent(&ev, xw.win)) continue;
4862 if (handler[ev.type]) (handler[ev.type])(&ev);
4866 if (term != NULL) {
4867 switch (closeRequestComes) {
4868 case 1: // just comes
4869 if (opt_ignoreclose == 0) {
4870 tcmdlinehide();
4871 tcmdlineinitex("Do you really want to close sterm [Y/n]? ");
4872 term->cmdexecfn = cmdline_closequeryexec;
4873 } else if (opt_ignoreclose < 0) {
4874 //FIXME: kill all clients?
4875 return;
4877 closeRequestComes = 0;
4878 break;
4879 case 2: // ok, die now
4880 //FIXME: kill all clients?
4881 return;
4885 if (!ptrBlanked && opt_ptrblank > 0 && mclock_ticks()-ptrLastMove >= opt_ptrblank) {
4886 xblankPointer();
4892 ////////////////////////////////////////////////////////////////////////////////
4893 typedef const char * (*IniHandlerFn) (const char *optname, const char *fmt, char *argstr, void *udata);
4896 typedef struct {
4897 const char *name;
4898 const char *fmt;
4899 void *udata;
4900 IniHandlerFn fn;
4901 } IniCommand;
4904 static const char *inifnGenericOneArg (const char *optname, const char *fmt, char *argstr, void *udata) {
4905 return iniParseArguments(argstr, fmt, udata);
4909 static const char *inifnGenericOneStr (const char *optname, const char *fmt, char *argstr, void *udata) {
4910 char *s = NULL;
4911 const char *err = iniParseArguments(argstr, fmt, &s);
4913 if (err != NULL) return err;
4914 if ((s = strdup(s)) == NULL) return "out of memory";
4915 if (udata) {
4916 char **ustr = (char **)udata;
4918 if (*ustr) free(*ustr);
4919 *ustr = s;
4921 return NULL;
4925 static const char *inifnTabPosition (const char *optname, const char *fmt, char *argstr, void *udata) {
4926 int newpos = -1;
4927 const char *err = NULL;
4929 while (argstr[0]) {
4930 char *s = NULL;
4932 if ((err = iniParseArguments(argstr, "R-", &argstr)) != NULL) return err;
4933 if (!argstr[0]) break;
4935 if ((err = iniParseArguments(argstr, "s!-R-", &s, &argstr)) != NULL) return err;
4936 if (tolower(s[0]) == 't') newpos = 1;
4937 else if (tolower(s[0]) == 'b') newpos = 0;
4938 else if ((err = iniParseArguments(s, fmt, &newpos)) != NULL) return err;
4941 if (newpos == -1) return "invalid tabbar position";
4942 opt_tabposition = newpos;
4943 return NULL;
4947 static const IniCommand iniCommands[] = {
4948 {"term", "s!-", &opt_term, inifnGenericOneStr},
4949 {"class", "s!-", &opt_class, inifnGenericOneStr},
4950 {"title", "s!-", &opt_title, inifnGenericOneStr},
4951 {"fontnorm", "s!-", &opt_fontnorm, inifnGenericOneStr},
4952 {"fontbold", "s!-", &opt_fontbold, inifnGenericOneStr},
4953 {"fonttab", "s!-", &opt_fonttab, inifnGenericOneStr},
4954 {"shell", "s!-", &opt_shell, inifnGenericOneStr},
4955 {"doubleclicktimeout", "i{0,10000}", &opt_doubleclick_timeout, inifnGenericOneArg},
4956 {"tripleclicktimeout", "i{0,10000}", &opt_tripleclick_timeout, inifnGenericOneArg},
4957 {"tabsize", "i{1,256}", &opt_tabsize, inifnGenericOneArg},
4958 {"defaultfg", "i{0,511}", &defaultFG, inifnGenericOneArg},
4959 {"defaultbg", "i{0,511}", &defaultBG, inifnGenericOneArg},
4960 {"defaultcursorfg", "i{0,511}", &defaultCursorFG, inifnGenericOneArg},
4961 {"defaultcursorbg", "i{0,511}", &defaultCursorBG, inifnGenericOneArg},
4962 {"defaultinactivecursorfg", "i{0,511}", &defaultCursorInactiveFG, inifnGenericOneArg},
4963 {"defaultinactivecursorbg", "i{-1,511}", &defaultCursorInactiveBG, inifnGenericOneArg},
4964 {"defaultboldfg", "i{-1,511}", &defaultBoldFG, inifnGenericOneArg},
4965 {"defaultunderlinefg", "i{-1,511}", &defaultUnderlineFG, inifnGenericOneArg},
4966 {"normaltabfg", "i{0,511}", &normalTabFG, inifnGenericOneArg},
4967 {"normaltabbg", "i{0,511}", &normalTabBG, inifnGenericOneArg},
4968 {"activetabfg", "i{0,511}", &activeTabFG, inifnGenericOneArg},
4969 {"activetabbg", "i{0,511}", &activeTabBG, inifnGenericOneArg},
4970 {"maxhistory", "i{0,65535}", &opt_maxhistory, inifnGenericOneArg},
4971 {"ptrblank", "i{0,65535}", &opt_ptrblank, inifnGenericOneArg},
4972 {"tabcount", "i{1,128}", &opt_tabcount, inifnGenericOneArg},
4973 {"tabposition", "i{0,1}", &opt_tabposition, inifnTabPosition},
4974 {"drawtimeout", "i{5,30000}", &opt_drawtimeout, inifnGenericOneArg},
4975 {"audiblebell", "b", &opt_audiblebell, inifnGenericOneArg},
4976 {"urgentbell", "b", &opt_urgentbell, inifnGenericOneArg},
4977 {"cursorblink", "i{0,10000}", &opt_cursorBlink, inifnGenericOneArg},
4978 {"cursorblinkinactive", "b", &opt_cursorBlinkInactive, inifnGenericOneArg},
4979 {"drawunderline", "b", &opt_drawunderline, inifnGenericOneArg},
4980 {"ignoreclose", "i{-1,1}", &opt_ignoreclose, inifnGenericOneArg},
4981 {"maxdrawtimeout", "i{100,60000}", &opt_maxdrawtimeout, inifnGenericOneArg},
4982 {NULL, NULL, NULL, NULL}
4986 #define MISC_CMD_NONE ((const char *)-1)
4989 // NULL: command processed; MISC_CMD_NONE: unknown command; !NULL: error
4990 static const char *processMiscCmds (const char *optname, char *argstr) {
4991 const char *err = NULL;
4993 if (strcasecmp(optname, "unimap") == 0) {
4994 int uni, ch;
4995 char *alt = NULL;
4997 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
4998 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
4999 if (unimap) free(unimap);
5000 unimap = NULL;
5001 return NULL;
5004 //unimap 0x2592 0x61 alt
5005 if ((err = iniParseArguments(argstr, "i{0,65535}i{0,255}|s!-", &uni, &ch, &alt)) != NULL) return err;
5006 if (alt != NULL && strcasecmp(alt, "alt") != 0) return "invalid unimap";
5007 if (unimap == NULL) {
5008 if ((unimap = calloc(65536, sizeof(unimap[0]))) == NULL) return "out of memory";
5010 if (alt != NULL && ch == 0) alt = NULL;
5011 if (alt != NULL && ch < 96) return "invalid unimap";
5012 unimap[uni] = ch;
5013 if (alt != NULL) unimap[uni] |= 0x8000;
5014 return NULL;
5017 if (strcasecmp(optname, "keytrans") == 0) {
5018 char *src = NULL, *dst = NULL;
5020 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
5021 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
5022 keytrans_reset();
5023 return NULL;
5026 if ((err = iniParseArguments(argstr, "s!-s!-", &src, &dst)) != NULL) return err;
5027 keytrans_add(src, dst);
5028 return NULL;
5031 if (strcasecmp(optname, "keybind") == 0) {
5032 char *key = NULL, *act = NULL;
5033 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
5034 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
5035 keybinds_reset();
5036 return NULL;
5039 if ((err = iniParseArguments(argstr, "s!-R!", &key, &act)) != NULL) return err;
5040 keybind_add(key, act);
5041 return NULL;
5044 if (strcasecmp(optname, "keymap") == 0) {
5045 char *key = NULL, *str = NULL;
5047 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
5048 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
5049 keymap_reset();
5050 return NULL;
5053 if ((err = iniParseArguments(argstr, "s!-s!-", &key, &str)) != NULL) return err;
5054 keymap_add(key, str);
5055 return NULL;
5058 return MISC_CMD_NONE;
5062 #define INI_LINE_SIZE (32768)
5064 // <0: file not found
5065 // >0: file loading error
5066 // 0: ok
5067 static int loadConfig (const char *fname) {
5068 int inifelse = 0; // 0: not; 1: doing true; 2: doing false; -1: waiting else/endif; -2: waiting endif
5069 FILE *fi = fopen(fname, "r");
5070 const char *err = NULL;
5071 char *line;
5072 int lineno = 0;
5074 if (fi == NULL) return -1;
5075 if ((line = malloc(INI_LINE_SIZE)) == NULL) { err = "out of memory"; goto quit; }
5077 while (fgets(line, INI_LINE_SIZE-1, fi) != NULL) {
5078 char *optname, *argstr, *d;
5079 int goodoption = 0;
5081 ++lineno;
5082 line[INI_LINE_SIZE-1] = 0;
5083 // get option name
5084 for (optname = line; *optname && isspace(*optname); ++optname) ;
5085 if (!optname[0] || optname[0] == '#') continue; // comment
5086 if (!isalnum(optname[0])) { err = "invalid option name"; goto quit; }
5087 d = argstr = optname;
5088 while (*argstr) {
5089 if (!argstr[0] || isspace(argstr[0])) break;
5090 if (!isalnum(argstr[0]) && argstr[0] != '_' && argstr[0] != '.') { err = "invalid option name"; goto quit; }
5091 if (argstr[0] != '_') *d++ = tolower(*argstr);
5092 ++argstr;
5094 if (*argstr) ++argstr;
5095 *d = 0;
5096 if (!isalnum(optname[0])) { err = "invalid option name"; goto quit; }
5098 if (strcasecmp(optname, "ifterm") == 0) {
5099 char *val;
5101 if (inifelse != 0) { err = "nested ifs are not allowed"; goto quit; }
5102 inifelse = -1;
5103 if ((err = iniParseArguments(argstr, "s", &val)) != NULL) goto quit;
5104 if (strcasecmp(opt_term, val) == 0) inifelse = 1;
5105 continue;
5107 if (strcasecmp(optname, "else") == 0) {
5108 switch (inifelse) {
5109 case -1: inifelse = 2; break;
5110 case 2: case -2: case 0: err = "else without if"; goto quit;
5111 case 1: inifelse = -2; break;
5113 continue;
5115 if (strcasecmp(optname, "endif") == 0) {
5116 switch (inifelse) {
5117 case -1: case -2: case 1: case 2: inifelse = 0; break;
5118 case 0: err = "endif without if"; goto quit;
5120 continue;
5123 if (inifelse < 0) {
5124 //trimstr(argstr);
5125 //fprintf(stderr, "skip: [%s]\n", argstr);
5126 continue;
5128 if (opt_term_locked && strcasecmp(optname, "term") == 0) continue; // termname given in command line
5129 // ok, we have option name in `optname` and arguments in `argstr`
5130 if (strncmp(optname, "color.", 6) == 0) {
5131 int n = 0;
5132 char *s = NULL;
5134 optname += 6;
5135 if (!optname[0]) { err = "invalid color option"; goto quit; }
5136 while (*optname) {
5137 if (!isdigit(*optname)) { err = "invalid color option"; goto quit; }
5138 n = (n*10)+(optname[0]-'0');
5139 ++optname;
5141 if (n < 0 || n > 511) { err = "invalid color index"; goto quit; }
5143 if ((err = iniParseArguments(argstr, "s!-", &s)) != NULL) goto quit;
5144 if ((s = strdup(s)) == NULL) { err = "out of memory"; goto quit; }
5145 if (opt_colornames[n] != NULL) free(opt_colornames[n]);
5146 opt_colornames[n] = s;
5147 continue;
5150 if ((err = processMiscCmds(optname, argstr)) != MISC_CMD_NONE) {
5151 if (err != NULL) goto quit;
5152 continue;
5153 } else {
5154 err = NULL;
5157 for (int f = 0; iniCommands[f].name != NULL; ++f) {
5158 if (strcmp(iniCommands[f].name, optname) == 0) {
5159 if ((err = iniCommands[f].fn(optname, iniCommands[f].fmt, argstr, iniCommands[f].udata)) != NULL) goto quit;
5160 goodoption = 1;
5161 break;
5164 if (!goodoption) {
5165 fprintf(stderr, "ini error at line %d: unknown option '%s'!\n", lineno, optname);
5168 quit:
5169 if (line != NULL) free(line);
5170 fclose(fi);
5171 if (err == NULL && inifelse != 0) err = "if without endif";
5172 if (err != NULL) die("ini error at line %d: %s", lineno, err);
5173 return 0;
5177 static void initDefaultOptions (void) {
5178 opt_title = strdup("sterm");
5179 opt_class = strdup("sterm");
5180 opt_term = strdup(TNAME);
5181 opt_fontnorm = strdup(FONT);
5182 opt_fontbold = strdup(FONTBOLD);
5183 opt_fonttab = strdup(FONTTAB);
5184 opt_shell = strdup(SHELL);
5186 memset(opt_colornames, 0, sizeof(opt_colornames));
5187 for (int f = 0; f < LEN(defcolornames); ++f) opt_colornames[f] = strdup(defcolornames[f]);
5188 for (int f = 0; f < LEN(defextcolornames); ++f) opt_colornames[f+256] = strdup(defextcolornames[f]);
5190 keytrans_add("KP_Home", "Home");
5191 keytrans_add("KP_Left", "Left");
5192 keytrans_add("KP_Up", "Up");
5193 keytrans_add("KP_Right", "Right");
5194 keytrans_add("KP_Down", "Down");
5195 keytrans_add("KP_Prior", "Prior");
5196 keytrans_add("KP_Next", "Next");
5197 keytrans_add("KP_End", "End");
5198 keytrans_add("KP_Begin", "Begin");
5199 keytrans_add("KP_Insert", "Insert");
5200 keytrans_add("KP_Delete", "Delete");
5202 keybind_add("shift+Insert", "PastePrimary");
5203 keybind_add("alt+Insert", "PasteCliboard");
5204 keybind_add("ctrl+alt+t", "NewTab");
5205 keybind_add("ctrl+alt+Left", "SwitchToTab prev");
5206 keybind_add("ctrl+alt+Right", "SwitchToTab next");
5208 keymap_add("BackSpace", "\177");
5209 keymap_add("Insert", "\x1b[2~");
5210 keymap_add("Delete", "\x1b[3~");
5211 keymap_add("Home", "\x1b[1~");
5212 keymap_add("End", "\x1b[4~");
5213 keymap_add("Prior", "\x1b[5~");
5214 keymap_add("Next", "\x1b[6~");
5215 keymap_add("F1", "\x1bOP");
5216 keymap_add("F2", "\x1bOQ");
5217 keymap_add("F3", "\x1bOR");
5218 keymap_add("F4", "\x1bOS");
5219 keymap_add("F5", "\x1b[15~");
5220 keymap_add("F6", "\x1b[17~");
5221 keymap_add("F7", "\x1b[18~");
5222 keymap_add("F8", "\x1b[19~");
5223 keymap_add("F9", "\x1b[20~");
5224 keymap_add("F10", "\x1b[21~");
5225 keymap_add("Up", "\x1bOA");
5226 keymap_add("Down", "\x1bOB");
5227 keymap_add("Right", "\x1bOC");
5228 keymap_add("Left", "\x1bOD");
5232 ////////////////////////////////////////////////////////////////////////////////
5233 static Term *oldTerm;
5234 static int oldTermIdx;
5235 static Term *newTerm;
5236 static int newTermIdx;
5237 static int newTermSwitch;
5240 typedef void (*CmdHandlerFn) (const char *cmdname, char *argstr);
5242 typedef struct {
5243 const char *name;
5244 CmdHandlerFn fn;
5245 } Command;
5248 static void cmdPastePrimary (const char *cmdname, char *argstr) {
5249 selpaste(XA_PRIMARY);
5253 static void cmdPasteSecondary (const char *cmdname, char *argstr) {
5254 selpaste(XA_SECONDARY);
5258 static void cmdPasteClipboard (const char *cmdname, char *argstr) {
5259 selpaste(XA_CLIPBOARD);
5263 static void cmdExec (const char *cmdname, char *argstr) {
5264 if (term != NULL) {
5265 if (term->execcmd != NULL) free(term->execcmd);
5266 term->execcmd = (argstr[0] ? strdup(argstr) : NULL);
5271 static int parseTabArgs (char *argstr, int *noswitch, int nowrap, int idx) {
5272 for (;;) {
5273 char *arg;
5275 while (*argstr && isspace(*argstr)) ++argstr;
5276 if (!argstr[0]) break;
5277 if (iniParseArguments(argstr, "s-R-", &arg, &argstr) != NULL) break;
5279 if (strcasecmp(arg, "noswitch") == 0) *noswitch = 1;
5280 else if (strcasecmp(arg, "switch") == 0) *noswitch = 0;
5281 else if (strcasecmp(arg, "nowrap") == 0) nowrap = 1;
5282 else if (strcasecmp(arg, "wrap") == 0) nowrap = 0;
5283 else if (strcasecmp(arg, "first") == 0) idx = 0;
5284 else if (strcasecmp(arg, "last") == 0) idx = term_count-1;
5285 else if (strcasecmp(arg, "prev") == 0) idx = -1;
5286 else if (strcasecmp(arg, "next") == 0) idx = -2;
5287 else {
5288 long int n = -1;
5289 char *eptr;
5291 n = strtol(arg, &eptr, 0);
5292 if (!eptr[0] && n >= 0 && n < term_count) idx = n;
5295 switch (idx) {
5296 case -1: // prev
5297 if ((idx = termidx-1) < 0) idx = nowrap ? 0 : term_count-1;
5298 break;
5299 case -2: // next
5300 if ((idx = termidx+1) >= term_count) idx = nowrap ? term_count-1 : 0;
5301 break;
5303 return idx;
5307 static void flushNewTerm (void) {
5308 if (newTerm != NULL) {
5309 if (newTermSwitch && term != NULL) term->lastActiveTime = mclock_ticks();
5310 term = newTerm;
5311 termidx = newTermIdx;
5312 tinitialize(term_array[0]->col, term_array[0]->row);
5314 term->picbufh = term->row*xw.ch;
5315 term->picbufw = term->col*xw.cw;
5316 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
5317 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
5319 if (ttynew(term) != 0) {
5320 term = oldTerm;
5321 termidx = oldTermIdx;
5322 termfree(newTermIdx);
5323 } else {
5324 selinit();
5325 ttyresize();
5326 if (newTermSwitch) {
5327 term = NULL;
5328 termidx = 0;
5329 switchToTerm(newTermIdx, 1);
5330 oldTerm = term;
5331 oldTermIdx = termidx;
5332 } else {
5333 term = oldTerm;
5334 termidx = oldTermIdx;
5337 newTerm = NULL;
5342 static void cmdNewTab (const char *cmdname, char *argstr) {
5343 int noswitch = 0, idx;
5345 if (opt_disabletabs) return;
5346 flushNewTerm();
5347 if ((newTerm = termalloc()) == NULL) return;
5348 /*idx =*/ parseTabArgs(argstr, &noswitch, 0, termidx);
5349 idx = term_count-1;
5350 if (!noswitch) {
5351 if (term != NULL) term->lastActiveTime = mclock_ticks();
5352 oldTermIdx = idx;
5354 newTermIdx = termidx = idx;
5355 term = newTerm;
5356 newTermSwitch = !noswitch;
5360 static void cmdCloseTab (const char *cmdname, char *argstr) {
5361 flushNewTerm();
5362 if (term != NULL && !term->dead) kill(term->pid, SIGTERM);
5366 static void cmdKillTab (const char *cmdname, char *argstr) {
5367 flushNewTerm();
5368 if (!term->dead) kill(term->pid, SIGKILL);
5372 static void cmdSwitchToTab (const char *cmdname, char *argstr) {
5373 int noswitch = 0, idx;
5375 flushNewTerm();
5376 idx = parseTabArgs(argstr, &noswitch, 0, -666);
5377 if (idx >= 0) {
5378 switchToTerm(idx, 1);
5379 oldTerm = term;
5380 oldTermIdx = termidx;
5385 static void cmdMoveTabTo (const char *cmdname, char *argstr) {
5386 int noswitch = 0, idx;
5388 flushNewTerm();
5389 idx = parseTabArgs(argstr, &noswitch, 0, termidx);
5390 if (idx != termidx && idx >= 0 && idx < term_count) {
5391 Term *t = term_array[termidx];
5393 // remove current term
5394 for (int f = termidx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
5395 // insert term
5396 for (int f = term_count-2; f >= idx; --f) term_array[f+1] = term_array[f];
5397 term_array[idx] = t;
5398 termidx = idx;
5399 oldTerm = t;
5400 oldTermIdx = idx;
5401 updateTabBar = 1;
5406 static void cmdDefaultFG (const char *cmdname, char *argstr) {
5407 char *s = NULL;
5408 int c;
5410 if (iniParseArguments(argstr, "i{0,511}|s-", &c, &s) == NULL) {
5411 if (s != NULL && tolower(s[0]) == 'g') defaultFG = c; else term->deffg = c;
5416 static void cmdDefaultBG (const char *cmdname, char *argstr) {
5417 char *s = NULL;
5418 int c;
5420 if (iniParseArguments(argstr, "i{0,511}|s-", &c) == NULL) {
5421 if (s != NULL && tolower(s[0]) == 'g') defaultBG = c; else term->defbg = c;
5426 static void scrollHistory (int delta) {
5427 if (term->maxhistory < 1) return; // no history
5428 term->topline += delta;
5429 if (term->topline > term->maxhistory) term->topline = term->maxhistory;
5430 if (term->topline < 0) term->topline = 0;
5431 tfulldirt();
5432 draw(1);
5436 static void cmdScrollHistoryLineUp (const char *cmdname, char *argstr) {
5437 scrollHistory(1);
5441 static void cmdScrollHistoryPageUp (const char *cmdname, char *argstr) {
5442 scrollHistory(term->row);
5446 static void cmdScrollHistoryLineDown (const char *cmdname, char *argstr) {
5447 scrollHistory(-1);
5451 static void cmdScrollHistoryPageDown (const char *cmdname, char *argstr) {
5452 scrollHistory(-term->row);
5456 static void cmdScrollHistoryTop (const char *cmdname, char *argstr) {
5457 scrollHistory(term->linecount);
5461 static void cmdScrollHistoryBottom (const char *cmdname, char *argstr) {
5462 scrollHistory(-term->linecount);
5466 static void cmdUTF8Locale (const char *cmdname, char *argstr) {
5467 int b;
5469 if (iniParseArguments(argstr, "b", &b) == NULL) {
5470 if (!needConversion) b = 1;
5471 if (term != NULL) term->needConv = !b;
5472 //fprintf(stderr, "needConv: %d (%d)\n", term->needConv, needConversion);
5477 static void cmdCommandMode (const char *cmdname, char *argstr) {
5478 if (term != NULL) {
5479 if (term->cmdMode == CMDMODE_NONE) tcmdlineinit(); else tcmdlinehide();
5484 // [show|hide]
5485 static void cmdCursor (const char *cmdname, char *argstr) {
5486 if (term != NULL) {
5487 char *s;
5489 if (iniParseArguments(argstr, "s!-", &s) != NULL) {
5490 tcmdlinemsg((term->c.state&CURSOR_HIDE) ? "cursor is hidden" : "cursor is visible");
5491 } else {
5492 if (strcasecmp(s, "show") == 0) term->c.state &= ~CURSOR_HIDE;
5493 else if (strcasecmp(s, "hide") == 0) term->c.state |= CURSOR_HIDE;
5494 term->wantRedraw = 1;
5500 static void cmdResetAttrs (const char *cmdname, char *argstr) {
5501 if (term != NULL) {
5502 term->c.attr.attr &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
5503 term->c.attr.attr |= ATTR_DEFFG | ATTR_DEFBG;
5504 term->c.attr.fg = term->deffg;
5505 term->c.attr.bg = term->defbg;
5510 static void cmdResetCharset (const char *cmdname, char *argstr) {
5511 if (term != NULL) {
5512 term->mode &= ~(MODE_GFX0|MODE_GFX1);
5513 term->charset = MODE_GFX0;
5518 // [norm|alt]
5519 static void cmdScreen (const char *cmdname, char *argstr) {
5520 if (term != NULL) {
5521 char *s;
5523 if (iniParseArguments(argstr, "s!-", &s) != NULL) {
5524 tcmdlinemsg(IS_SET(MODE_ALTSCREEN) ? "screen: alt" : "screen: norm");
5525 } else {
5526 if (strcasecmp(s, "norm") == 0) {
5527 if (IS_SET(MODE_ALTSCREEN)) tswapscreen();
5528 } else if (strcasecmp(s, "alt") == 0) {
5529 if (!IS_SET(MODE_ALTSCREEN)) tswapscreen();
5536 // [on|off]
5537 static void cmdMouseReports (const char *cmdname, char *argstr) {
5538 if (term != NULL) {
5539 int b;
5541 if (iniParseArguments(argstr, "b", &b) != NULL) {
5542 tcmdlinemsg(IS_SET(MODE_MOUSE) ? "mouse reports are on" : "mouse reports are off");
5543 } else {
5544 if (b) term->mode |= MODE_MOUSEBTN; else term->mode &= ~MODE_MOUSEBTN;
5550 static int cmd_parseIntArg (const char *fmt, char *argstr, int *b, int *global, int *toggle) {
5551 while (argstr[0]) {
5552 char *s = NULL;
5554 if (iniParseArguments(argstr, "R-", &argstr) != NULL) break;
5555 if (!argstr[0]) break;
5557 if (iniParseArguments(argstr, "s!-R-", &s, &argstr) != NULL) break;
5558 if (global && tolower(s[0]) == 'g') {
5559 *global = 1;
5560 } else if (toggle && tolower(s[0]) == 't') {
5561 *toggle = 1;
5562 } else {
5563 if (!b || iniParseArguments(s, fmt, b) != NULL) return -1;
5566 return 0;
5570 static void cmdAudibleBell (const char *cmdname, char *argstr) {
5571 int b = -1, toggle = 0, global = 0;
5573 if (term == NULL) return;
5574 if (cmd_parseIntArg("b", argstr, &b, &global, &toggle) != 0) return;
5576 if (b == -1) {
5577 tcmdlinemsg((global ? opt_audiblebell : (term->belltype&BELL_AUDIO)) ? "AudibleBell: 1" : "AudibleBell: 0");
5578 } else {
5579 if (toggle) {
5580 if (global) opt_audiblebell = !opt_audiblebell; else term->belltype ^= BELL_AUDIO;
5581 } else {
5582 if (global) opt_audiblebell = b; else term->belltype = (term->belltype&~BELL_AUDIO)|(b!=0?BELL_AUDIO:0);
5588 static void cmdUrgentBell (const char *cmdname, char *argstr) {
5589 int b = -1, toggle = 0, global = 0;
5591 if (term == NULL) return;
5592 if (cmd_parseIntArg("b", argstr, &b, &global, &toggle) != 0) return;
5594 if (b == -1) {
5595 tcmdlinemsg((global ? opt_urgentbell : (term->belltype&BELL_URGENT)) ? "UrgentBell: 1" : "UrgentBell: 0");
5596 } else {
5597 if (toggle) {
5598 if (global) opt_urgentbell = !opt_urgentbell; else term->belltype ^= BELL_URGENT;
5599 } else {
5600 if (global) opt_urgentbell = b; else term->belltype = (term->belltype&~BELL_URGENT)|(b!=0?BELL_URGENT:0);
5606 static void cmdMonochrome (const char *cmdname, char *argstr) {
5607 int b = -1, global = 0;
5609 if (term == NULL) return;
5610 if (cmd_parseIntArg("i{0,3}", argstr, &b, &global, NULL) != 0) return;
5612 if (b == -1) {
5613 char buf[32];
5615 b = (global ? globalBW : term->blackandwhite);
5616 sprintf(buf, "Monochrome: %d", b);
5617 tcmdlinemsg(buf);
5618 } else {
5619 if (global) {
5620 if (b == 3) tcmdlinemsg("Monochrome-global can't be '3'!");
5621 else globalBW = b;
5622 } else {
5623 term->blackandwhite = b;
5626 tfulldirt();
5627 updateTabBar = 1;
5631 static void cmdCursorBlink (const char *cmdname, char *argstr) {
5632 int b = -1, global = 0;
5634 if (term == NULL) return;
5635 if (cmd_parseIntArg("i{0,10000}", argstr, &b, &global, NULL) != 0) return;
5637 if (b == -1) {
5638 char buf[32];
5640 b = (global ? opt_cursorBlink : term->curblink);
5641 sprintf(buf, "CursorBlink: %d", b);
5642 tcmdlinemsg(buf);
5643 } else {
5644 if (global) {
5645 opt_cursorBlink = b;
5646 } else {
5647 term->curblink = b;
5648 term->curbhidden = 0;
5651 tfulldirt();
5652 updateTabBar = 1;
5656 static void cmdCursorBlinkInactive (const char *cmdname, char *argstr) {
5657 int b = -1, global = 0, toggle = 0, *iptr;
5659 if (term == NULL) return;
5660 if (cmd_parseIntArg("b", argstr, &b, &global, &toggle) != 0) return;
5662 iptr = (global ? &opt_cursorBlinkInactive : &term->curblinkinactive);
5663 if (b != -1) {
5664 if (toggle) *iptr = !(*iptr); else *iptr = b;
5665 draw(0);
5670 static void cmdIgnoreClose (const char *cmdname, char *argstr) {
5671 int b = -666;
5673 if (term == NULL) return;
5674 if (cmd_parseIntArg("i{-1,1}", argstr, &b, NULL, NULL) != 0) return;
5676 if (b == -666) {
5677 char buf[32];
5679 sprintf(buf, "IgnoreClose: %d", opt_ignoreclose);
5680 tcmdlinemsg(buf);
5681 } else {
5682 opt_ignoreclose = b;
5687 static void cmdMaxHistory (const char *cmdname, char *argstr) {
5688 int b = -1, global = 0;
5690 if (term == NULL) return;
5691 if (cmd_parseIntArg("i{0,65535}", argstr, &b, &global, NULL) != 0) return;
5693 if (b == -1) {
5694 char buf[32];
5696 sprintf(buf, "MaxHistory: %d", (global?opt_maxhistory:term->maxhistory));
5697 tcmdlinemsg(buf);
5698 } else {
5699 if (!global) tadjustmaxhistory(b); else opt_maxhistory = b;
5704 static void cmdMaxDrawTimeout (const char *cmdname, char *argstr) {
5705 int b = -1;
5707 if (term == NULL) return;
5708 if (cmd_parseIntArg("i{100,60000}", argstr, &b, NULL, NULL) != 0) return;
5710 if (b == -1) {
5711 char buf[32];
5713 sprintf(buf, "MaxDrawTimeout: %d", opt_maxdrawtimeout);
5714 tcmdlinemsg(buf);
5715 } else {
5716 opt_maxdrawtimeout = b;
5721 static void cmdTabPosition (const char *cmdname, char *argstr) {
5722 int newpos = -1;
5724 while (argstr[0]) {
5725 char *s = NULL;
5727 if (iniParseArguments(argstr, "R-", &argstr) != NULL) break;
5728 if (!argstr[0]) break;
5730 if (iniParseArguments(argstr, "s!-R-", &s, &argstr) != NULL) break;
5731 if (tolower(s[0]) == 't') newpos = 1;
5732 else if (tolower(s[0]) == 'b') newpos = 0;
5733 else if (iniParseArguments(s, "i{0,1}", &newpos) != NULL) return;
5736 if (newpos == -1) {
5737 char buf[32];
5739 sprintf(buf, "TabPostion: %s", (opt_tabposition == 0 ? "bottom" : "top"));
5740 tcmdlinemsg(buf);
5741 } else if (opt_tabposition != newpos) {
5742 opt_tabposition = newpos;
5743 updateTabBar = 1;
5744 tfulldirt();
5745 draw(1);
5750 static void cmdTabCount (const char *cmdname, char *argstr) {
5751 int b = -1;
5753 if (term == NULL) return;
5754 if (cmd_parseIntArg("i{1,128}", argstr, &b, NULL, NULL) != 0) return;
5756 if (b == -1) {
5757 char buf[32];
5759 sprintf(buf, "TabCount: %d", opt_tabcount);
5760 tcmdlinemsg(buf);
5761 } else if (opt_tabcount != b) {
5762 opt_tabcount = b;
5763 fixFirstTab();
5764 draw(1);
5769 static const Command commandList[] = {
5770 {"PastePrimary", cmdPastePrimary},
5771 {"PasteSecondary", cmdPasteSecondary},
5772 {"PasteClipboard", cmdPasteClipboard},
5773 {"exec", cmdExec},
5774 {"NewTab", cmdNewTab}, // 'noswitch' 'next' 'prev' 'first' 'last'
5775 {"CloseTab", cmdCloseTab},
5776 {"KillTab", cmdKillTab},
5777 {"SwitchToTab", cmdSwitchToTab}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5778 {"MoveTabTo", cmdMoveTabTo}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5779 {"defaultfg", cmdDefaultFG},
5780 {"defaultbg", cmdDefaultBG},
5781 {"ScrollHistoryLineUp", cmdScrollHistoryLineUp},
5782 {"ScrollHistoryPageUp", cmdScrollHistoryPageUp},
5783 {"ScrollHistoryLineDown", cmdScrollHistoryLineDown},
5784 {"ScrollHistoryPageDown", cmdScrollHistoryPageDown},
5785 {"ScrollHistoryTop", cmdScrollHistoryTop},
5786 {"ScrollHistoryBottom", cmdScrollHistoryBottom},
5787 {"UTF8Locale", cmdUTF8Locale}, // 'on', 'off'
5788 {"AudibleBell", cmdAudibleBell},
5789 {"UrgentBell", cmdUrgentBell},
5790 {"CommandMode", cmdCommandMode},
5791 {"Cursor", cmdCursor},
5792 {"ResetAttrs", cmdResetAttrs},
5793 {"ResetCharset", cmdResetCharset},
5794 {"Screen", cmdScreen},
5795 {"MouseReports", cmdMouseReports},
5796 {"Monochrome", cmdMonochrome},
5797 {"Mono", cmdMonochrome},
5798 {"CursorBlink", cmdCursorBlink},
5799 {"CursorBlinkInactive", cmdCursorBlinkInactive},
5800 {"IgnoreClose", cmdIgnoreClose},
5801 {"MaxHistory", cmdMaxHistory},
5802 {"MaxDrawTimeout", cmdMaxDrawTimeout},
5803 {"TabPosition", cmdTabPosition},
5804 {"TabCount", cmdTabCount},
5806 {"term", cmdTermName},
5807 {"title", cmdWinTitle},
5808 {"tabsize", cmdTabSize},
5809 {"defaultcursorfg", cmdDefaultCursorFG},
5810 {"defaultcursorbg", cmdDefaultCursorBG},
5811 {"defaultinactivecursorfg", cmdDefaultInactiveCursorFG},
5812 {"defaultinactivecursorbg", cmdDefaultInactiveCursorBG},
5813 {"defaultboldfg", cmdDefaultBoldFG},
5814 {"defaultunderlinefg", cmdDefaultUnderlineFG},
5816 {NULL, NULL}
5820 static const char *findCommandCompletion (const char *str, int slen, const char *prev) {
5821 const char *res = NULL;
5822 int phit = 0;
5824 if (slen < 1) return NULL;
5825 for (int f = 0; commandList[f].name != NULL; ++f) {
5826 if (strlen(commandList[f].name) >= slen && strncasecmp(commandList[f].name, str, slen) == 0) {
5827 if (prev == NULL || phit) return commandList[f].name;
5828 if (strcasecmp(commandList[f].name, prev) == 0) phit = 1;
5829 if (res == NULL) res = commandList[f].name;
5832 return res;
5836 // !0: NewTab command
5837 static int executeCommand (const char *str, int slen) {
5838 const char *e;
5839 char *cmdname;
5840 int cmdfound = 0;
5842 if (str == NULL) return 0;
5843 if (slen < 0) slen = strlen(str);
5845 for (int f = 0; f < slen; ++f) if (!str[f]) { slen = f; break; }
5847 while (slen > 0 && isspace(*str)) { ++str; --slen; }
5848 if (slen < 1 || !str[0]) return 0;
5850 for (e = str; slen > 0 && !isspace(*e); ++e, --slen) ;
5852 if (e-str > 127) return 0;
5853 cmdname = alloca(e-str+8);
5854 if (cmdname == NULL) return 0;
5855 memcpy(cmdname, str, e-str);
5856 cmdname[e-str] = 0;
5857 if (opt_disabletabs && strcasecmp(cmdname, "NewTab") == 0) return 1;
5859 while (slen > 0 && isspace(*e)) { ++e; --slen; }
5860 //FIXME: ugly copypaste!
5862 for (int f = 0; commandList[f].name != NULL; ++f) {
5863 if (strcasecmp(commandList[f].name, cmdname) == 0) {
5864 char *left = calloc(slen+2, 1);
5866 if (left != NULL) {
5867 if (slen > 0) memcpy(left, e, slen);
5868 //fprintf(stderr, "command: [%s]; args: [%s]\n", cmdname, left);
5869 commandList[f].fn(cmdname, left);
5870 free(left);
5872 cmdfound = 1;
5873 break;
5877 if (!cmdfound) {
5878 char *left = calloc(slen+2, 1);
5880 if (left != NULL) {
5881 if (slen > 0) memcpy(left, e, slen);
5882 processMiscCmds(cmdname, left);
5883 free(left);
5887 return 0;
5892 static const char *cmdpSkipStr (const char *str) {
5893 while (*str && isspace(*str)) ++str;
5894 if (str[0] && str[0] != ';') {
5895 char qch = ' ';
5897 while (*str) {
5898 if (*str == ';' && qch == ' ') break;
5899 if (qch != ' ' && *str == qch) { qch = ' '; ++str; continue; }
5900 if (*str == '"' || *str == '\'') {
5901 if (qch == ' ') qch = *str;
5902 ++str;
5903 continue;
5905 if (*str++ == '\\' && *str) ++str;
5908 return str;
5913 static void executeCommands (const char *str) {
5914 oldTerm = term;
5915 oldTermIdx = termidx;
5916 newTerm = NULL;
5917 newTermSwitch = 0;
5918 if (str == NULL) return;
5919 while (*str) {
5920 const char *ce;
5921 char qch;
5923 while (*str && isspace(*str)) ++str;
5924 if (!*str) break;
5925 if (*str == ';') { ++str; continue; }
5927 ce = str;
5928 qch = ' ';
5929 while (*ce) {
5930 if (*ce == ';' && qch == ' ') break;
5931 if (qch != ' ' && *ce == qch) { qch = ' '; ++ce; continue; }
5932 if (*ce == '"' || *ce == '\'') {
5933 if (qch == ' ') qch = *ce;
5934 ++ce;
5935 continue;
5937 if (*ce++ == '\\' && *ce) ++ce;
5940 if (executeCommand(str, ce-str)) break;
5941 if (*ce) str = ce+1; else break;
5943 flushNewTerm();
5944 switchToTerm(oldTermIdx, 1);
5948 ////////////////////////////////////////////////////////////////////////////////
5949 int main (int argc, char *argv[]) {
5950 char *configfile = NULL;
5951 char *runcmd = NULL;
5953 //dbgLogInit();
5955 for (int f = 1; f < argc; f++) {
5956 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
5957 if (strcmp(argv[f], "-into") == 0) { ++f; continue; }
5958 if (strcmp(argv[f], "-embed") == 0) { ++f; continue; }
5959 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
5960 case 'e': f = argc+1; break;
5961 case 't':
5962 case 'c':
5963 case 'w':
5964 case 'b':
5965 case 'R':
5966 ++f;
5967 break;
5968 case 'T':
5969 if (++f < argc) {
5970 free(opt_term);
5971 opt_term = strdup(argv[f]);
5972 opt_term_locked = 1;
5974 break;
5975 case 'C':
5976 if (++f < argc) {
5977 if (configfile != NULL) free(configfile);
5978 configfile = strdup(argv[f]);
5980 break;
5981 case 'l':
5982 if (++f < argc) cliLocale = argv[f];
5983 break;
5984 case 'S': // single-tab mode
5985 opt_disabletabs = 1;
5986 break;
5987 case 'v':
5988 case 'h':
5989 default:
5990 fprintf(stderr, "%s", USAGE);
5991 exit(EXIT_FAILURE);
5995 initDefaultOptions();
5996 if (configfile == NULL) {
5997 const char *home = getenv("HOME");
5999 if (home != NULL) {
6000 configfile = SPrintf("%s/.sterm.rc", home);
6001 if (loadConfig(configfile) == 0) goto cfgdone;
6002 free(configfile); configfile = NULL;
6004 configfile = SPrintf("%s/.config/sterm.rc", home);
6005 if (loadConfig(configfile) == 0) goto cfgdone;
6006 free(configfile); configfile = NULL;
6009 configfile = SPrintf("/etc/sterm.rc");
6010 if (loadConfig(configfile) == 0) goto cfgdone;
6011 free(configfile); configfile = NULL;
6013 configfile = SPrintf("/etc/sterm/sterm.rc");
6014 if (loadConfig(configfile) == 0) goto cfgdone;
6015 free(configfile); configfile = NULL;
6017 configfile = SPrintf("./.sterm.rc");
6018 if (loadConfig(configfile) == 0) goto cfgdone;
6019 free(configfile); configfile = NULL;
6020 // no config
6021 } else {
6022 if (loadConfig(configfile) < 0) die("config file '%s' not found!", configfile);
6024 cfgdone:
6025 if (configfile != NULL) free(configfile); configfile = NULL;
6027 for (int f = 1; f < argc; f++) {
6028 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
6029 if (strcmp(argv[f], "-into") == 0 || strcmp(argv[f], "-embed") == 0) {
6030 if (opt_embed) free(opt_embed);
6031 opt_embed = strdup(argv[f]);
6032 continue;
6034 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
6035 case 't':
6036 if (++f < argc) {
6037 free(opt_title);
6038 opt_title = strdup(argv[f]);
6040 break;
6041 case 'c':
6042 if (++f < argc) {
6043 free(opt_class);
6044 opt_class = strdup(argv[f]);
6046 break;
6047 case 'w':
6048 if (++f < argc) {
6049 if (opt_embed) free(opt_embed);
6050 opt_embed = strdup(argv[f]);
6052 break;
6053 case 'R':
6054 if (++f < argc) runcmd = argv[f];
6055 break;
6056 case 'e':
6057 /* eat every remaining arguments */
6058 if (++f < argc) opt_cmd = &argv[f];
6059 f = argc+1;
6060 case 'T':
6061 case 'C':
6062 case 'l':
6063 ++f;
6064 break;
6065 case 'S':
6066 break;
6067 case 'v':
6068 case 'h':
6069 default:
6070 fprintf(stderr, "%s", USAGE);
6071 exit(EXIT_FAILURE);
6075 setenv("TERM", opt_term, 1);
6076 mclock_init();
6077 setlocale(LC_ALL, "");
6078 initLCConversion();
6079 updateTabBar = 1;
6080 termidx = 0;
6081 term = termalloc();
6082 if (term->execcmd != NULL) { free(term->execcmd); term->execcmd = NULL; }
6083 tinitialize(80, 25);
6084 if (ttynew(term) != 0) die("can't run process");
6085 opt_cmd = NULL;
6086 xinit();
6087 selinit();
6088 if (runcmd != NULL) executeCommands(runcmd);
6089 run();
6090 return 0;