1 /* See LICENSE for licence details. */
2 #define VERSION "0.3.0"
7 #define _XOPEN_SOURCE 600
23 #include <sys/ioctl.h>
24 #include <sys/select.h>
27 #include <sys/types.h>
31 #include <X11/Xatom.h>
33 #include <X11/Xutil.h>
34 #include <X11/cursorfont.h>
35 #include <X11/keysym.h>
38 //#define DUMP_KEYSYMS
42 //#define DUMP_PROG_OUTPUT
43 //#define DUMP_PROG_INPUT
45 //#define DUMP_IO_READ
46 //#define DUMP_IO_WRITE
48 #if defined(DUMP_IO_READ) || defined(DUMP_IO_WRITE)
53 # define DUMP_KEYPAD_SWITCH(strflag,strstate) do { fprintf(stderr, "KEYPAD %s (%s)\n", (strstate), (strflag)); } while (0)
55 # define DUMP_KEYPAD_SWITCH(strflag,strstate) ((void)(sizeof(strflag)+sizeof(strstate)))
59 ////////////////////////////////////////////////////////////////////////////////
61 "sterm " VERSION " (c) 2010-2012 st engineers and Ketmar // Vampire Avalon\n" \
62 "usage: sterm [-t title] [-c class] [-w windowid] [-T termname] [-C config_file] [-v] [-e command...]\n"
65 ////////////////////////////////////////////////////////////////////////////////
66 #define FONT "-*-liberation mono-medium-*-*-*-17-*-*-*-*-*-koi8-u"
67 #define BOLDFONT "-*-liberation mono-medium-*-*-*-17-*-*-*-*-*-koi8-u"
68 #define FONTTAB "-*-helvetica-*-*-*-*-12-*-*-*-*-*-*-*"
70 #define FONT "-*-terminus-bold-*-*-*-20-*-*-*-*-*-koi8-u"
71 #define BOLDFONT "-*-terminus-bold-*-*-*-20-*-*-*-*-*-koi8-u"
75 /* Space in pixels around the terminal buffer */
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
[] = {
105 /* 8 normal colors */
114 /* 8 bright colors */
127 /* more colors can be added after 255 to use with DefaultXX */
128 static const char *defextcolornames
[] = {
131 /* root terminal fg and bg */
134 /* bold and underline */
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 */
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)
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 SERRNO strerror(errno)
182 #define MIN(a, b) ((a) < (b) ? (a) : (b))
183 #define MAX(a, b) ((a) < (b) ? (b) : (a))
184 #define LEN(a) (sizeof(a)/sizeof(a[0]))
185 #define DEFAULT(a, b) (a) = ((a) ? (a) : (b))
186 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
187 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
188 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
189 #define IS_SET(flag) (term->mode&(flag))
190 #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000+(t1.tv_usec-t2.tv_usec)/1000)
191 #define X2COL(x) (((x)-opt_border)/xw.cw)
192 #define Y2ROW(y) (((y)-opt_border)/xw.ch-(term!=NULL?term->topline:0))
193 #define IS_GFX(mode) (((mode)&(ATTR_GFX|ATTR_G1)) == ATTR_GFX || ((mode)&(ATTR_GFX1|ATTR_G1)) == (ATTR_GFX1|ATTR_G1))
196 ////////////////////////////////////////////////////////////////////////////////
197 enum glyph_attribute
{
200 ATTR_UNDERLINE
= 0x02,
209 enum cursor_movement
{
225 GLYPH_SET
= 0x01, /* for selection only */
233 MODE_APPKEYPAD
= 0x04,
234 MODE_ALTSCREEN
= 0x08,
236 MODE_MOUSEBTN
= 0x20,
237 MODE_MOUSEMOTION
= 0x40,
238 MODE_MOUSE
= 0x20|0x40,
247 ESC_ALTCHARSET
= 0x10,
261 enum { B0
=1, B1
=2, B2
=4, B3
=8, B4
=16, B5
=32, B6
=64, B7
=128 };
264 ////////////////////////////////////////////////////////////////////////////////
265 typedef unsigned char uchar
;
266 typedef unsigned int uint
;
267 typedef unsigned long ulong
;
268 typedef unsigned short ushort
;
271 typedef struct __attribute__((packed
)) {
272 char c
[UTF_SIZ
]; /* character code */
273 uchar mode
; /* attribute flags */
274 ushort fg
; /* foreground */
275 ushort bg
; /* background */
276 uchar state
; /* state flags */
283 Glyph attr
; /* current char attributes */
290 /* CSI Escape sequence structs */
291 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
293 char buf
[ESC_BUF_SIZ
]; /* raw string */
294 int len
; /* raw string length */
296 int arg
[ESC_ARG_SIZ
];
297 int narg
; /* nb of args */
302 /* Purely graphic info */
311 int w
; /* window width */
312 int h
; /* window height */
313 int bufw
; /* pixmap width */
314 int bufh
; /* pixmap height */
315 int ch
; /* char height */
316 int cw
; /* char width */
317 char state
; /* focus, redraw, visible */
319 int tch
; /* tab text char height */
322 //struct timeval lastdraw;
326 /* TODO: use better name for vars... */
331 struct { int x
, y
; } b
, e
;
334 struct timeval tclick1
;
335 struct timeval tclick2
;
339 /* Drawing Context */
341 ulong
*col
; //[LEN(colorname) < 256 ? 256 : LEN(colorname)];
353 /* Internal representation of the screen */
358 int row
; /* nb row */
359 int col
; /* nb col */
360 int topline
; /* top line for drawing (0: no history; 1: show one history line; etc) */
361 int linecount
; /* full, with history */
362 int maxhistory
;/* max history lines; 0: none; <0: infinite */
363 Line
*line
; /* screen */
364 Line
*alt
; /* alternate screen */
365 char *dirty
; /* dirtyness of lines */
366 TCursor c
; /* cursor */
367 int top
; /* top scroll limit */
368 int bot
; /* bottom scroll limit */
369 int mode
; /* terminal mode flags */
370 int esc
; /* escape state flags */
372 TCursor csaved
; /* saved cursor info */
373 // old cursor position
377 char title
[ESC_TITLE_SIZ
+1];
385 #ifdef DUMP_PROG_OUTPUT
393 char drawbuf
[DRAW_BUF_SIZ
];
418 ////////////////////////////////////////////////////////////////////////////////
420 static uchar
*unimap
= NULL
; // 0 or 65535 items; 127: special; high bit set: use alt(gfx) mode; 0: empty
422 static char **opt_cmd
= NULL
;
423 static char *opt_title
= NULL
;
424 static char *opt_embed
= NULL
;
425 static char *opt_class
= NULL
;
426 static char *opt_term
= NULL
;
427 static char *opt_fontnorm
= NULL
;
428 static char *opt_fontbold
= NULL
;
429 static char *opt_fonttab
= NULL
;
430 static char *opt_shell
= NULL
;
431 static char *opt_colornames
[512];
432 static int opt_term_locked
= 0;
433 static int opt_doubleclick_timeout
= DOUBLECLICK_TIMEOUT
;
434 static int opt_tripleclick_timeout
= TRIPLECLICK_TIMEOUT
;
435 static int opt_tabsize
= TAB
;
436 static int opt_border
= BORDER
;
437 static int defaultFG
= DEFAULT_FG
;
438 static int defaultBG
= DEFAULT_BG
;
439 static int defaultCursorFG
= 0;
440 static int defaultCursorBG
= DEFAULT_CS
;
441 static int defaultCursorInactiveFG
= 0;
442 static int defaultCursorInactiveBG
= DEFAULT_UCS
;
443 static int defaultBoldFG
= -1;
444 static int defaultUnderlineFG
= -1;
445 static int normalTabFG
= 258;
446 static int normalTabBG
= 257;
447 static int activeTabFG
= 258;
448 static int activeTabBG
= 0;
449 static int opt_maxhistory
= 512;
452 static Term
**term_array
= NULL
;
453 static int term_count
= 0;
454 static int term_array_size
= 0;
455 static Term
*term
; // current terminal
456 static int termidx
; // current terminal index; DON'T RELAY ON IT!
457 static int updateTabBar
;
462 static Atom XA_VT_SELECTION
;
463 static Atom XA_CLIPBOARD
;
465 static Atom XA_TARGETS
;
466 static Atom XA_NETWM_NAME
;
469 ////////////////////////////////////////////////////////////////////////////////
476 static KeyTransDef
*keytrans
= NULL
;
477 static int keytrans_size
= 0;
478 static int keytrans_used
= 0;
489 static KeyInfoDef
*keybinds
= NULL
;
490 static int keybinds_size
= 0;
491 static int keybinds_used
= 0;
493 static KeyInfoDef
*keymap
= NULL
;
494 static int keymap_size
= 0;
495 static int keymap_used
= 0;
498 ////////////////////////////////////////////////////////////////////////////////
499 static void executeCommands (const char *str
);
501 static void ttyresize (void);
502 static void tputc (const char *c
); // `c` is utf-8
503 static void ttywrite (const char *s
, size_t n
);
504 static void tsetdirt (int top
, int bot
);
505 static void tfulldirt (void);
506 static void draw (int forced
);
507 static void xseturgency (int add
);
510 static inline void ttywritestr (const char *s
) { if (s
!= NULL
&& s
[0]) ttywrite(s
, strlen(s
)); }
512 static void xfixsel (void);
515 ////////////////////////////////////////////////////////////////////////////////
517 static void trimstr (char *s
) {
520 while (*s
&& isspace(*s
)) ++s
;
521 for (e
= s
+strlen(s
); e
> s
; --e
) if (!isspace(e
[-1])) break;
522 if (e
<= s
) *s
= 0; else *e
= 0;
526 // parse the argument list
527 // return error message or NULL
530 // 's': string (char *)
531 // 'i': integer (int *)
532 // 'b': boolean (int *)
533 // '|': optional arguments follows
534 // '.': stop parsing, ignore rest
535 // 'R': stop parsing, set rest ptr (char *)
536 // string modifiers (also for 'R'):
537 // '!' -- don't allow empty strings
538 // '-' -- trim spaces
543 // WARNING! `line` will be modified!
544 // WARNING! string pointers will point INSIDE `line`, so don't discard it!
546 const char *iniParseArguments (char *line
, const char *fmt
, ...) {
550 if (line
== NULL
) return "alas";
554 char spec
= *fmt
++, *args
;
556 if (spec
== '|') { inOptional
= 1; continue; }
557 if (spec
== '.') { va_end(ap
); return NULL
; }
559 while (*line
&& isspace(*line
)) ++line
;
560 if (*line
== '#') *line
= 0;
563 char **p
= va_arg(ap
, char **);
567 if (*fmt
== '!') { ++fmt
; noempty
= 1; }
568 else if (*fmt
== '-') { ++fmt
; trimstr(line
); }
571 if (noempty
&& !line
[0]) return "invalid empty arg";
572 if (p
!= NULL
) *p
= line
;
578 // end of line, stop right here
580 if (!inOptional
) return "out of args";
586 char *dest
= args
, qch
= '#';
589 if (line
[0] == '"' || line
[0] == '\'') qch
= *line
++;
591 while (*line
&& *line
!= qch
) {
592 if (qch
== '#' && isspace(*line
)) break;
596 case 'n': *dest
++ = '\n'; ++line
; break;
597 case 'r': *dest
++ = '\r'; ++line
; break;
598 case 't': *dest
++ = '\t'; ++line
; break;
599 case 'a': *dest
++ = '\a'; ++line
; break;
600 case 'e': *dest
++ = '\x1b'; ++line
; break; // esc
601 case 's': *dest
++ = ' '; ++line
; break;
604 if (!isxdigit(*line
)) { va_end(ap
); return "invalid hex escape"; }
605 n
= toupper(*line
)-'0'; if (n
> 9) n
-= 7;
607 if (isxdigit(*line
)) {
608 int b
= toupper(*line
)-'0'; if (b
> 9) b
-= 7;
617 for (int f
= 0; f
< 4; ++f
) {
618 if (*line
< '0' || *line
> '7') break;
619 n
= (n
*8)+(line
[0]-'0');
620 if (n
> 255) { va_end(ap
); return "invalid oct escape"; }
623 if (n
== 0) { va_end(ap
); return "invalid oct escape"; }
626 case '1'...'9': // decimal
628 for (int f
= 0; f
< 3; ++f
) {
629 if (*line
< '0' || *line
> '9') break;
630 n
= (n
*8)+(line
[0]-'0');
631 if (n
> 255) { va_end(ap
); return "invalid dec escape"; }
634 if (n
== 0) { va_end(ap
); return "invalid oct escape"; }
646 if (*line
!= qch
) return "unfinished string";
648 } else if (*line
!= '#') ++line
;
651 // now process and convert argument
655 case 's': { /* string */
656 int noempty
= 0, trim
= 0;
660 if (*fmt
== '!') { noempty
= 1; ++fmt
; }
661 else if (*fmt
== '-') { trim
= 1; ++fmt
; }
665 if (trim
) trimstr(args
);
667 if (noempty
&& !args
[0]) { va_end(ap
); return "invalid empty string"; }
668 p
= va_arg(ap
, char **);
669 if (p
!= NULL
) *p
= args
;
674 return "invalid integer";
676 int *p
= va_arg(ap
, int *);
681 n
= strtol(args
, &eptr
, 0);
682 if (*eptr
) { va_end(ap
); return "invalid integer"; }
686 int minmax
[2], haveminmax
[2];
688 haveminmax
[0] = haveminmax
[1] = 0;
689 minmax
[0] = minmax
[1] = 0;
691 for (int f
= 0; f
< 2; ++f
) {
692 if (isdigit(*fmt
) || *fmt
== '-' || *fmt
== '+') {
695 if (*fmt
== '-') neg
= 1;
696 if (!isdigit(*fmt
)) {
698 if (!isdigit(*fmt
)) { va_end(ap
); return "invalid integer bounds"; }
700 while (isdigit(*fmt
)) {
701 minmax
[f
] = minmax
[f
]*10+(fmt
[0]-'0');
704 if (neg
) minmax
[f
] = -minmax
[f
];
705 //fprintf(stderr, "got: %d\n", minmax[f]);
708 if (f
== 1) { va_end(ap
); return "invalid integer bounds: extra comma"; }
709 // do nothing, we are happy
711 } else if (*fmt
== '}') {
714 } else { va_end(ap
); return "invalid integer bounds"; }
716 if (*fmt
!= '}') { va_end(ap
); return "invalid integer bounds"; }
719 //fprintf(stderr, "b: (%d,%d) (%d,%d)\n", haveminmax[0], minmax[0], haveminmax[1], minmax[1]);
720 if ((haveminmax
[0] && n
< minmax
[0]) || (haveminmax
[1] && n
> minmax
[1])) { va_end(ap
); return "integer out of bounds"; }
726 case 'b': { /* bool */
727 int *p
= va_arg(ap
, int *);
730 if (!args
[0]) { va_end(ap
); return "invalid boolean"; }
731 if (strcasecmp(args
, "true") == 0 || strcasecmp(args
, "on") == 0 ||
732 strcasecmp(args
, "tan") == 0 || strcasecmp(args
, "1") == 0) {
734 } else if (strcasecmp(args
, "false") == 0 || strcasecmp(args
, "off") == 0 ||
735 strcasecmp(args
, "ona") == 0 || strcasecmp(args
, "0") == 0) {
740 return "invalid boolean";
745 return "invalid format specifier";
749 while (*line
&& isspace(*line
)) ++line
;
750 if (!line
[0] || line
[0] == '#') return NULL
;
755 ////////////////////////////////////////////////////////////////////////////////
757 static int utf8decode (const char *s
, long *u
) {
763 if (~c
& B7
) { /* 0xxxxxxx */
766 } else if ((c
& (B7
|B6
|B5
)) == (B7
|B6
)) { /* 110xxxxx */
767 *u
= c
&(B4
|B3
|B2
|B1
|B0
);
769 } else if ((c
& (B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
)) { /* 1110xxxx */
770 *u
= c
&(B3
|B2
|B1
|B0
);
772 } else if ((c
& (B7
|B6
|B5
|B4
|B3
)) == (B7
|B6
|B5
|B4
)) { /* 11110xxx */
779 for (int f
= n
; f
> 0; --f
, ++rtn
, ++s
) {
781 if ((c
& (B7
|B6
)) != B7
) goto invalid
; /* 10xxxxxx */
783 *u
|= c
& (B5
|B4
|B3
|B2
|B1
|B0
);
785 if ((n
== 1 && *u
< 0x80) ||
786 (n
== 2 && *u
< 0x800) ||
787 (n
== 3 && *u
< 0x10000) ||
788 (*u
>= 0xD800 && *u
<= 0xDFFF)) {
798 static int utf8encode (const long *u
, char *s
) {
806 *sp
= uc
; /* 0xxxxxxx */
808 } else if (*u
< 0x800) {
809 *sp
= (uc
>> 6) | (B7
|B6
); /* 110xxxxx */
811 } else if (uc
< 0x10000) {
812 *sp
= (uc
>> 12) | (B7
|B6
|B5
); /* 1110xxxx */
814 } else if (uc
<= 0x10FFFF) {
815 *sp
= (uc
>> 18) | (B7
|B6
|B5
|B4
); /* 11110xxx */
821 for (int f
= n
; f
> 0; --f
, ++sp
) *sp
= ((uc
>> 6*(f
-1)) & (B5
|B4
|B3
|B2
|B1
|B0
)) | B7
; /* 10xxxxxx */
832 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
833 UTF-8 otherwise return 0 */
834 static int isfullutf8 (const char *s
, int b
) {
841 if ((*c1
&(B7
|B6
|B5
)) == (B7
|B6
) && b
== 1) return 0;
842 if ((*c1
&(B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
) && (b
== 1 || (b
== 2 && (*c2
&(B7
|B6
)) == B7
))) return 0;
843 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;
848 static int utf8size (const char *s
) {
852 if ((c
&(B7
|B6
|B5
)) == (B7
|B6
)) return 2;
853 if ((c
&(B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
)) return 3;
858 ////////////////////////////////////////////////////////////////////////////////
860 static char *SPrintfVA (const char *fmt
, va_list vaorig
) {
865 if (buf
== NULL
) { fprintf(stderr
, "\nFATAL: out of memory!\n"); abort(); }
871 olen
= vsnprintf(buf
, len
, fmt
, va
);
873 if (olen
>= 0 && olen
< len
) return buf
;
874 if (olen
< 0) olen
= len
*2-1;
875 nb
= realloc(buf
, olen
+1);
876 if (nb
== NULL
) { fprintf(stderr
, "\nFATAL: out of memory!\n"); abort(); }
883 static __attribute__((format(printf
,1,2))) char *SPrintf (const char *fmt
, ...) {
888 buf
= SPrintfVA(fmt
, va
);
894 static __attribute__((noreturn
)) __attribute__((format(printf
,1,2))) void die (const char *errstr
, ...) {
897 fprintf(stderr
, "FATAL: ");
898 va_start(ap
, errstr
);
899 vfprintf(stderr
, errstr
, ap
);
901 fprintf(stderr
, "\n");
906 ////////////////////////////////////////////////////////////////////////////////
907 static void fixWindowTitle (const Term
*t
) {
908 const char *title
= (t
!= NULL
) ? t
->title
: NULL
;
910 if (title
== NULL
|| !title
[0]) {
912 if (title
== NULL
) title
= "";
914 XStoreName(xw
.dpy
, xw
.win
, title
);
915 XChangeProperty(xw
.dpy
, xw
.win
, XA_NETWM_NAME
, XA_UTF8
, 8, PropModeReplace
, (const unsigned char *)title
, strlen(title
));
919 static void switchToTerm (int idx
, int redraw
) {
920 if (idx
>= 0 && idx
< term_count
&& term_array
[idx
] != NULL
&& term_array
[idx
] != term
) {
922 term
= term_array
[termidx
];
925 fixWindowTitle(term
);
929 //fprintf(stderr, "term #%d\n", termidx);
934 static Term
*termalloc (void) {
937 if (term_count
>= term_array_size
) {
938 int newsz
= (term_count
==0) ? 1 : term_array_size
+64;
939 Term
**n
= realloc(term_array
, sizeof(Term
*)*newsz
);
941 if (n
== NULL
&& term_count
== 0) die("out of memory!");
943 term_array_size
= newsz
;
945 if ((t
= calloc(1, sizeof(Term
))) == NULL
) return NULL
;
946 t
->wrbufsize
= WBUFSIZ
;
947 t
->deffg
= defaultFG
;
948 t
->defbg
= defaultBG
;
950 term_array
[term_count
++] = t
;
955 // newer delete last terminal!
956 static void termfree (int idx
) {
957 if (idx
>= 0 && idx
< term_count
&& term_array
[idx
] != NULL
) {
958 Term
*t
= term_array
[idx
];
961 kill(t
->pid
, SIGKILL
);
968 if (idx
== termidx
) {
969 if (term_count
> 1) {
970 switchToTerm((idx
> 0) ? idx
-1 : 1, 0);
976 for (int y
= 0; y
< t
->row
; ++y
) free(t
->alt
[y
]);
977 for (int y
= 0; y
< t
->linecount
; ++y
) {
978 //fprintf(stderr, "y=%d\n", y);
984 if (t
->execcmd
!= NULL
) free(t
->execcmd
);
987 // not current, and current at the right
990 for (int f
= idx
+1; f
< term_count
; ++f
) term_array
[f
-1] = term_array
[f
];
992 XFreePixmap(xw
.dpy
, t
->picbuf
);
1000 static void termcleanup (void) {
1003 while (f
< term_count
) {
1004 if (term_array
[f
]->dead
) {
1013 //FIXME: is it safe to assume that signal interrupted main program?
1014 static void sigchld (int a
) {
1015 //if (waitpid(term->pid, &stat, 0) < 0) die("waiting for pid %hd failed: %s", term->pid, SERRNO);
1018 pid_t res
= waitpid(-1, &stat
, WNOHANG
);
1020 if (res
== (pid_t
)-1 || res
== 0) break;
1021 //if (WIFEXITED(stat)) exit(WEXITSTATUS(stat));
1022 //exit(EXIT_FAILURE);
1023 for (int f
= 0; f
< term_count
; ++f
) {
1024 if (term_array
[f
]->pid
== res
) {
1025 // this terminal should die
1026 //if (term_count == 1) exit(0);
1027 //close(term_array[f]->cmdfd);
1028 //term_array[f]->cmdfd = -1;
1029 term_array
[f
]->dead
= 1;
1030 term_array
[f
]->pid
= 0;
1036 signal(SIGCHLD
, sigchld
);
1040 ////////////////////////////////////////////////////////////////////////////////
1041 static void keytrans_reset (void) {
1042 if (keytrans
) free(keytrans
);
1049 static void keytrans_add (const char *src
, const char *dst
) {
1050 KeySym kssrc
= XStringToKeysym(src
);
1051 KeySym ksdst
= XStringToKeysym(dst
);
1053 if (kssrc
== NoSymbol
) die("invalid keysym: '%s'", src
);
1054 if (ksdst
== NoSymbol
) die("invalid keysym: '%s'", dst
);
1055 if (kssrc
== ksdst
) return; // idiot
1057 for (int f
= 0; f
< keytrans_used
; ++f
) {
1058 if (keytrans
[f
].src
== kssrc
) {
1060 keytrans
[f
].dst
= ksdst
;
1065 if (keytrans_used
>= keytrans_size
) {
1066 int newsize
= keytrans_size
+64;
1067 KeyTransDef
*n
= realloc(keytrans
, sizeof(KeyTransDef
)*newsize
);
1069 if (n
== NULL
) die("out of memory");
1070 keytrans_size
= newsize
;
1073 keytrans
[keytrans_used
].src
= kssrc
;
1074 keytrans
[keytrans_used
].dst
= ksdst
;
1079 ////////////////////////////////////////////////////////////////////////////////
1080 static void parsekeyname (const char *str
, KeySym
*ks
, uint
*mask
, int *kp
) {
1081 char *s
= alloca(strlen(str
)+1);
1083 if (s
== NULL
) die("out of memory");
1094 while (*s
&& isspace(*s
)) ++s
;
1095 for (e
= s
; *e
&& !isspace(*e
) && *e
!= '+'; ++e
) ;
1098 if (strcasecmp(s
, "alt") == 0) mm
= Mod1Mask
;
1099 else if (strcasecmp(s
, "win") == 0) mm
= Mod4Mask
;
1100 else if (strcasecmp(s
, "ctrl") == 0) mm
= ControlMask
;
1101 else if (strcasecmp(s
, "shift") == 0) mm
= ShiftMask
;
1102 else if (strcasecmp(s
, "any") == 0) mm
= XK_NO_MOD
; //!
1103 else if (strcasecmp(s
, "kpad") == 0) *kp
= 1;
1106 if ((*ks
= XStringToKeysym(s
)) == NoSymbol
) break;
1107 //fprintf(stderr, "[%s]\n", s);
1112 while (*s
&& isspace(*s
)) ++s
;
1114 if (*s
!= '+') { *ks
= NoSymbol
; break; }
1117 if (mm
== XK_NO_MOD
) *mask
= XK_ANY_MOD
;
1118 else if (*mask
== XK_NO_MOD
) *mask
= mm
;
1119 else if (*mask
!= XK_ANY_MOD
) *mask
|= mm
;
1122 if (*s
) { *ks
= NoSymbol
; break; }
1125 if (*ks
== NoSymbol
) die("invalid key name: '%s'", str
);
1126 //fprintf(stderr, "mask=0x%08x, kp=%d\n", *mask, *kp);
1130 ////////////////////////////////////////////////////////////////////////////////
1131 static void keybinds_reset (void) {
1132 if (keybinds
) free(keybinds
);
1139 static void keybind_add (const char *key
, const char *act
) {
1144 parsekeyname(key
, &ks
, &mask
, &kp
);
1146 for (int f
= 0; f
< keybinds_used
; ++f
) {
1147 if (keybinds
[f
].key
== ks
&& keybinds
[f
].mask
== mask
) {
1148 // replace or remove
1149 free(keybinds
[f
].str
);
1150 if (act
== NULL
|| !act
[0]) {
1152 for (int c
= f
+1; c
< keybinds_used
; ++c
) keybinds
[c
-1] = keybinds
[c
];
1155 if ((keybinds
[f
].str
= strdup(act
)) == NULL
) die("out of memory");
1161 if (keybinds_used
>= keybinds_size
) {
1162 int newsize
= keybinds_size
+64;
1163 KeyInfoDef
*n
= realloc(keybinds
, sizeof(KeyInfoDef
)*newsize
);
1165 if (n
== NULL
) die("out of memory");
1166 keybinds_size
= newsize
;
1169 keybinds
[keybinds_used
].key
= ks
;
1170 keybinds
[keybinds_used
].mask
= mask
;
1171 keybinds
[keybinds_used
].kp
= 0;
1172 if ((keybinds
[keybinds_used
].str
= strdup(act
)) == NULL
) die("out of memory");
1177 ////////////////////////////////////////////////////////////////////////////////
1178 static void keymap_reset (void) {
1179 if (keymap
) free(keymap
);
1186 static void keymap_add (const char *key
, const char *act
) {
1191 parsekeyname(key
, &ks
, &mask
, &kp
);
1193 for (int f
= 0; f
< keymap_used
; ++f
) {
1194 if (keymap
[f
].key
== ks
&& keymap
[f
].mask
== mask
&& keymap
[f
].kp
== kp
) {
1195 // replace or remove
1196 free(keymap
[f
].str
);
1199 for (int c
= f
+1; c
< keymap_used
; ++c
) keymap
[c
-1] = keymap
[c
];
1202 if ((keymap
[f
].str
= strdup(act
)) == NULL
) die("out of memory");
1208 if (keymap_used
>= keymap_size
) {
1209 int newsize
= keymap_size
+128;
1210 KeyInfoDef
*n
= realloc(keymap
, sizeof(KeyInfoDef
)*newsize
);
1212 if (n
== NULL
) die("out of memory");
1213 keymap_size
= newsize
;
1216 keymap
[keymap_used
].key
= ks
;
1217 keymap
[keymap_used
].mask
= mask
;
1218 keymap
[keymap_used
].kp
= kp
;
1219 if ((keymap
[keymap_used
].str
= strdup(act
)) == NULL
) die("out of memory");
1224 ////////////////////////////////////////////////////////////////////////////////
1225 // locale conversions
1226 static iconv_t icFromLoc
;
1227 static iconv_t icToLoc
;
1228 static int needConversion
= 0;
1231 static void initLCConversion (void) {
1232 const char *lct
= setlocale(LC_CTYPE
, NULL
);
1236 if (strrchr(lct
, '.')) lct
= strrchr(lct
, '.')+1;
1237 if (strcasecmp(lct
, "utf8") == 0 || strcasecmp(lct
, "utf-8") == 0) return;
1238 //fprintf(stderr, "locale: [%s]\n", lct);
1239 icFromLoc
= iconv_open("UTF-8", lct
);
1240 if (icFromLoc
== (iconv_t
)-1) die("can't initialize locale conversion");
1241 cstr
= SPrintf("%s//TRANSLIT", lct
);
1242 icToLoc
= iconv_open(cstr
, "UTF-8");
1244 if (icToLoc
== (iconv_t
)-1) die("can't initialize locale conversion");
1249 static int loc2utf (char *dest
, const char *src
, int len
) {
1250 if (needConversion
) {
1258 il
= iconv(icFromLoc
, &ibuf
, &il
, &obuf
, &ol
);
1259 if (il
== (size_t)-1) return 0;
1262 if (len
> 0) memmove(dest
, src
, len
);
1268 static int utf2loc (char *dest
, const char *src
, int len
) {
1269 if (needConversion
) {
1277 il
= iconv(icToLoc
, &ibuf
, &il
, &obuf
, &ol
);
1278 if (il
== (size_t)-1) return 0;
1281 if (len
> 0) memmove(dest
, src
, len
);
1287 ////////////////////////////////////////////////////////////////////////////////
1289 static struct timespec mclk_sttime
; // starting time of monotonic clock
1292 static void mclock_init (void) {
1293 clock_gettime(CLOCK_MONOTONIC
/*CLOCK_MONOTONIC_RAW*/, &mclk_sttime
);
1297 static int mclock_ticks (void) {
1300 clock_gettime(CLOCK_MONOTONIC
/*CLOCK_MONOTONIC_RAW*/, &tp
);
1301 tp
.tv_sec
-= mclk_sttime
.tv_sec
;
1302 return tp
.tv_sec
*1000+(tp
.tv_nsec
/1000000);
1306 ////////////////////////////////////////////////////////////////////////////////
1308 static void inline markDirty (int lineno
, int flag
) {
1309 if (term
!= NULL
&& lineno
>= 0 && lineno
< term
->row
) {
1310 term
->dirty
[lineno
] |= flag
;
1311 term
->wantRedraw
= 1;
1316 static void selinit (void) {
1317 memset(&term
->sel
.tclick1
, 0, sizeof(term
->sel
.tclick1
));
1318 memset(&term
->sel
.tclick2
, 0, sizeof(term
->sel
.tclick2
));
1321 term
->sel
.clip
= NULL
;
1322 term
->sel
.xtarget
= XA_UTF8
;
1323 if (term
->sel
.xtarget
== None
) term
->sel
.xtarget
= XA_STRING
;
1327 static inline int selected (int x
, int y
) {
1328 if (term
->sel
.ey
== y
&& term
->sel
.by
== y
) {
1329 int bx
= MIN(term
->sel
.bx
, term
->sel
.ex
);
1330 int ex
= MAX(term
->sel
.bx
, term
->sel
.ex
);
1332 return BETWEEN(x
, bx
, ex
);
1335 ((term
->sel
.b
.y
< y
&& y
< term
->sel
.e
.y
) || (y
== term
->sel
.e
.y
&& x
<= term
->sel
.e
.x
)) ||
1336 (y
== term
->sel
.b
.y
&& x
>= term
->sel
.b
.x
&& (x
<= term
->sel
.e
.x
|| term
->sel
.b
.y
!= term
->sel
.e
.y
));
1340 static void getbuttoninfo (XEvent
*e
, int *b
, int *x
, int *y
) {
1341 if (b
) *b
= e
->xbutton
.button
;
1342 *x
= X2COL(e
->xbutton
.x
);
1343 *y
= Y2ROW(e
->xbutton
.y
);
1344 term
->sel
.b
.x
= term
->sel
.by
< term
->sel
.ey
? term
->sel
.bx
: term
->sel
.ex
;
1345 term
->sel
.b
.y
= MIN(term
->sel
.by
, term
->sel
.ey
);
1346 term
->sel
.e
.x
= term
->sel
.by
< term
->sel
.ey
? term
->sel
.ex
: term
->sel
.bx
;
1347 term
->sel
.e
.y
= MAX(term
->sel
.by
, term
->sel
.ey
);
1351 static void mousereport (XEvent
*e
) {
1352 int x
= X2COL(e
->xbutton
.x
);
1353 int y
= Y2ROW(e
->xbutton
.y
);
1354 int button
= e
->xbutton
.button
;
1355 int state
= e
->xbutton
.state
;
1356 //char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
1359 if (term
== NULL
) return;
1360 if (x
< 0 || x
>= term
->col
|| y
< 0 || y
>= term
->row
) return;
1361 sprintf(buf
, "\x1b[M%c%c%c", 0, 32+x
+1, 32+y
+1);
1363 if (e
->xbutton
.type
== MotionNotify
) {
1364 if (!IS_SET(MODE_MOUSEMOTION
) || (x
== term
->mouseox
&& y
== term
->mouseoy
)) return;
1365 button
= term
->mouseob
+32;
1368 } else if (e
->xbutton
.type
== ButtonRelease
|| button
== AnyButton
) {
1372 if (button
>= 3) button
+= 64-3;
1373 if (e
->xbutton
.type
== ButtonPress
) {
1374 term
->mouseob
= button
;
1379 buf
[3] = 32+button
+(state
& ShiftMask
? 4 : 0)+(state
& Mod4Mask
? 8 : 0)+(state
& ControlMask
? 16 : 0);
1384 static void xfixsel (void) {
1385 if (term
== NULL
) return;
1386 if (term
->sel
.clip
!= NULL
) {
1387 XSetSelectionOwner(xw
.dpy
, XA_PRIMARY
, xw
.win
, CurrentTime
);
1388 XSetSelectionOwner(xw
.dpy
, XA_CLIPBOARD
, xw
.win
, CurrentTime
);
1390 XSetSelectionOwner(xw
.dpy
, XA_PRIMARY
, None
, CurrentTime
);
1391 XSetSelectionOwner(xw
.dpy
, XA_CLIPBOARD
, None
, CurrentTime
);
1397 static void xsetsel (char *str
) {
1398 /* register the selection for both the clipboard and the primary */
1399 if (term
== NULL
) return;
1400 free(term
->sel
.clip
);
1401 term
->sel
.clip
= str
;
1403 //fprintf(stderr, "[%s]\n", str);
1407 static void selcopy (void) {
1409 int x
, y
, bufsize
, is_selected
= 0;
1411 if (term
== NULL
|| term
->sel
.bx
== -1) {
1414 bufsize
= (term
->col
+1)*(term
->sel
.e
.y
-term
->sel
.b
.y
+1)*UTF_SIZ
;
1415 ptr
= str
= malloc(bufsize
);
1416 /* append every set & selected glyph to the selection */
1417 for (y
= 0; y
< term
->row
; ++y
) {
1419 for (x
= 0; x
< term
->col
; ++x
) {
1420 is_selected
= selected(x
, y
);
1421 if (is_selected
) term
->line
[y
][x
].state
|= GLYPH_DIRTY
;
1422 if ((term
->line
[y
][x
].state
& GLYPH_SET
) && is_selected
) {
1423 int size
= utf8size(term
->line
[y
][x
].c
);
1424 memcpy(ptr
, term
->line
[y
][x
].c
, size
);
1428 /* \n at the end of every selected line except for the last one */
1429 if (is_selected
&& y
< term
->sel
.e
.y
) {
1440 static void selnotify (XEvent
*e
) {
1441 ulong nitems
, ofs
, rem
;
1445 XSelectionEvent
*se
= (XSelectionEvent
*)e
;
1448 if (term
== NULL
) return;
1453 fprintf(stderr, "selnotify!\n");
1455 name = se->selection != None ? XGetAtomName(se->display, se->selection) : NULL;
1456 fprintf(stderr, " selection: [%s]\n", name);
1457 if (name != NULL) XFree(name);
1459 name = se->target != None ? XGetAtomName(se->display, se->target) : NULL;
1460 fprintf(stderr, " target: [%s]\n", name);
1461 if (name != NULL) XFree(name);
1463 name = se->property != None ? XGetAtomName(se->display, se->property) : NULL;
1464 fprintf(stderr, " property: [%s]\n", name);
1465 if (name != NULL) XFree(name);
1469 if (se
->property
!= XA_VT_SELECTION
) return;
1471 fprintf(stderr, "selection:\n");
1472 fprintf(stderr, " primary: %d\n", se->selection == XA_PRIMARY);
1473 fprintf(stderr, " secondary: %d\n", se->selection == XA_SECONDARY);
1474 fprintf(stderr, " clipboard: %d\n", se->selection == XA_CLIPBOARD);
1475 fprintf(stderr, " vtsel: %d\n", se->selection == XA_VT_SELECTION);
1476 fprintf(stderr, "target:\n");
1477 fprintf(stderr, " primary: %d\n", se->target == XA_PRIMARY);
1478 fprintf(stderr, " secondary: %d\n", se->target == XA_SECONDARY);
1479 fprintf(stderr, " clipboard: %d\n", se->target == XA_CLIPBOARD);
1480 fprintf(stderr, " vtsel: %d\n", se->target == XA_VT_SELECTION);
1482 if (se
->target
== XA_UTF8
) {
1484 } else if (se
->target
== XA_STRING
) {
1489 if (!isutf8
) return;
1492 if (XGetWindowProperty(xw
.dpy
, xw
.win
, se
->property
, ofs
, BUFSIZ
/4, False
, AnyPropertyType
, &type
, &format
, &nitems
, &rem
, &data
)) {
1493 fprintf(stderr
, "Clipboard allocation failed\n");
1496 //fprintf(stderr, "nitems=%d; format=%d; rem=%d\n", (int)nitems, format, (int)rem);
1497 ttywrite((const char *)data
, nitems
*format
/8);
1499 /* number of 32-bit chunks returned */
1500 ofs
+= nitems
*format
/32;
1505 static void selpaste (Atom which
) {
1506 //fprintf(stderr, "selpaste %d!\n", which == XA_PRIMARY);
1507 if (term
== NULL
) return;
1508 //XConvertSelection(xw.dpy, XA_PRIMARY, term->sel.xtarget, XA_PRIMARY, xw.win, CurrentTime);
1509 if (XGetSelectionOwner(xw
.dpy
, which
) != None
) {
1510 XConvertSelection(xw
.dpy
, which
, term
->sel
.xtarget
, XA_VT_SELECTION
, xw
.win
, CurrentTime
);
1515 char *name = XGetAtomName(xw.dpy, which);
1517 fprintf(stderr, "no selection owner for [%s]!\n", name);
1521 if (which
== XA_PRIMARY
) selpaste(XA_SECONDARY
);
1522 else if (which
== XA_SECONDARY
) selpaste(XA_CLIPBOARD
);
1526 static void selrequest (XEvent
*e
) {
1527 XSelectionRequestEvent
*xsre
;
1528 XSelectionEvent xev
;
1530 if (term
== NULL
) return;
1531 xsre
= (XSelectionRequestEvent
*)e
;
1532 xev
.type
= SelectionNotify
;
1533 xev
.requestor
= xsre
->requestor
;
1534 xev
.selection
= xsre
->selection
;
1535 xev
.target
= xsre
->target
;
1536 xev
.time
= xsre
->time
;
1538 xev
.property
= None
;
1543 fprintf(stderr, "selrequest!\n");
1545 name = xsre->selection != None ? XGetAtomName(xsre->display, xsre->selection) : NULL;
1546 fprintf(stderr, " selection: [%s]\n", name);
1547 if (name != NULL) XFree(name);
1549 name = xsre->target != None ? XGetAtomName(xsre->display, xsre->target) : NULL;
1550 fprintf(stderr, " target: [%s]\n", name);
1551 if (name != NULL) XFree(name);
1553 name = xsre->property != None ? XGetAtomName(xsre->display, xsre->property) : NULL;
1554 fprintf(stderr, " property: [%s]\n", name);
1555 if (name != NULL) XFree(name);
1558 if (xsre
->target
== XA_TARGETS
) {
1559 /* respond with the supported type */
1560 Atom string
= XA_UTF8
;
1562 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, XA_ATOM
, 32, PropModeReplace
, (uchar
*)&string
, 1);
1563 xev
.property
= xsre
->property
;
1564 } else if (xsre
->target
== XA_UTF8
&& term
->sel
.clip
!= NULL
) {
1565 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, XA_UTF8
, 8, PropModeReplace
, (uchar
*)term
->sel
.clip
, strlen(term
->sel
.clip
));
1566 xev
.property
= xsre
->property
;
1567 } else if (xsre
->target
== XA_STRING
&& term
->sel
.clip
!= NULL
) {
1568 char *s
= malloc(strlen(term
->sel
.clip
)*4+8);
1571 int len
= utf2loc(s
, term
->sel
.clip
, strlen(term
->sel
.clip
));
1573 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, xsre
->target
, 8, PropModeReplace
, (uchar
*)s
, len
);
1574 xev
.property
= xsre
->property
;
1578 /* all done, send a notification to the listener */
1579 if (!XSendEvent(xsre
->display
, xsre
->requestor
, True
, 0, (XEvent
*)&xev
)) fprintf(stderr
, "Error sending SelectionNotify event\n");
1583 static void selclear (XEvent
*e
) {
1584 if (term
== NULL
) return;
1586 term
->sel
.ex
= term
->sel
.bx
= -1;
1587 term
->sel
.ey
= term
->sel
.by
= -1;
1593 static void bpress (XEvent
*e
) {
1594 if (term
== NULL
) return;
1595 if (e
->xbutton
.button
== Button1
&& (e
->xbutton
.state
&ShiftMask
) != 0) {
1596 if (term
->sel
.bx
!= -1) tsetdirt(term
->sel
.b
.y
, term
->sel
.e
.y
);
1598 term
->sel
.ex
= term
->sel
.bx
= X2COL(e
->xbutton
.x
);
1599 term
->sel
.ey
= term
->sel
.by
= Y2ROW(e
->xbutton
.y
);
1603 if (e
->xbutton
.button
== Button3
&& (e
->xbutton
.state
&ShiftMask
) != 0) {
1608 if (IS_SET(MODE_MOUSE
)) mousereport(e
);
1612 static void brelease (XEvent
*e
) {
1615 if (term
== NULL
) return;
1616 if ((e
->xbutton
.state
&ShiftMask
) != 0) reportit
= 0;
1617 if (reportit
&& IS_SET(MODE_MOUSE
)) {
1621 if (e
->xbutton
.button
== Button2
) {
1622 selpaste(XA_PRIMARY
);
1623 } else if (e
->xbutton
.button
== Button1
) {
1625 getbuttoninfo(e
, NULL
, &term
->sel
.ex
, &term
->sel
.ey
);
1626 if (term
->sel
.bx
== term
->sel
.ex
&& term
->sel
.by
== term
->sel
.ey
) {
1629 markDirty(term
->sel
.ey
, 2);
1631 gettimeofday(&now
, NULL
);
1632 if (TIMEDIFF(now
, term
->sel
.tclick2
) <= opt_tripleclick_timeout
) {
1633 /* triple click on the line */
1634 term
->sel
.b
.x
= term
->sel
.bx
= 0;
1635 term
->sel
.e
.x
= term
->sel
.ex
= term
->col
;
1636 term
->sel
.b
.y
= term
->sel
.e
.y
= term
->sel
.ey
;
1638 } else if (TIMEDIFF(now
, term
->sel
.tclick1
) <= opt_doubleclick_timeout
) {
1639 /* double click to select word */
1640 term
->sel
.bx
= term
->sel
.ex
;
1641 while (term
->sel
.bx
> 0 && term
->line
[term
->sel
.ey
][term
->sel
.bx
-1].state
& GLYPH_SET
&& term
->line
[term
->sel
.ey
][term
->sel
.bx
-1].c
[0] != ' ') --term
->sel
.bx
;
1642 term
->sel
.b
.x
= term
->sel
.bx
;
1643 while (term
->sel
.ex
< term
->col
-1 && term
->line
[term
->sel
.ey
][term
->sel
.ex
+1].state
& GLYPH_SET
&& term
->line
[term
->sel
.ey
][term
->sel
.ex
+1].c
[0] != ' ') ++term
->sel
.ex
;
1644 term
->sel
.e
.x
= term
->sel
.ex
;
1645 term
->sel
.b
.y
= term
->sel
.e
.y
= term
->sel
.ey
;
1652 memcpy(&term
->sel
.tclick2
, &term
->sel
.tclick1
, sizeof(struct timeval
));
1653 gettimeofday(&term
->sel
.tclick1
, NULL
);
1658 static void bmotion (XEvent
*e
) {
1659 if (term
== NULL
) return;
1660 if (term
->sel
.mode
) {
1661 int oldey
= term
->sel
.ey
, oldex
= term
->sel
.ex
;
1663 getbuttoninfo(e
, NULL
, &term
->sel
.ex
, &term
->sel
.ey
);
1664 if (oldey
!= term
->sel
.ey
|| oldex
!= term
->sel
.ex
) {
1665 int starty
= MIN(oldey
, term
->sel
.ey
);
1666 int endy
= MAX(oldey
, term
->sel
.ey
);
1668 tsetdirt(starty
, endy
);
1673 if (IS_SET(MODE_MOUSE
)) {
1680 ////////////////////////////////////////////////////////////////////////////////
1682 static inline void setWantRedraw (void) { if (term
!= NULL
) term
->wantRedraw
= 1; }
1686 static void dump (char c) {
1689 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
1690 if (++col % 10 == 0) fprintf(stderr, "\n");
1695 static __attribute__((noreturn
)) void execsh (const char *str
) {
1699 char *envshell
= getenv("SHELL");
1701 DEFAULT(envshell
, opt_shell
);
1702 setenv("TERM", opt_term
, 1);
1703 args
= opt_cmd
? opt_cmd
: (char *[]){envshell
, "-i", NULL
};
1707 args
= calloc(32768, sizeof(char *));
1708 if (args
== NULL
) exit(EXIT_FAILURE
);
1712 while (*str
&& isspace(*str
)) ++str
;
1716 while (*str
&& !isspace(*str
)) {
1717 if (*str
++ == '\\') {
1722 args
[argc
] = calloc(str
-b
+1, 1);
1723 memcpy(args
[argc
], b
, str
-b
);
1726 FILE *fo = fopen("z.log", "a");
1727 fprintf(fo, "%d: [%s]\n", argc, args[argc]);
1733 if (argc
< 1) exit(EXIT_FAILURE
);
1735 execvp(args
[0], args
);
1740 static int ttynew (Term
*term
) {
1742 struct winsize w
= {term
->row
, term
->col
, 0, 0};
1743 static int signalset
= 0;
1745 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0) die("openpty failed: %s", SERRNO
);
1749 switch (term
->pid
= fork()) {
1750 case -1: /* error */
1751 fprintf(stderr
, "fork failed");
1754 setsid(); /* create a new process group */
1755 dup2(s
, STDIN_FILENO
);
1756 dup2(s
, STDOUT_FILENO
);
1757 dup2(s
, STDERR_FILENO
);
1758 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0) die("ioctl TIOCSCTTY failed: %s", SERRNO
);
1761 execsh(term
->execcmd
);
1763 default: /* master */
1768 if (!signalset
) { signalset
= 1; signal(SIGCHLD
, sigchld
); }
1775 ////////////////////////////////////////////////////////////////////////////////
1777 static int ttycanread (void) {
1780 struct timeval timeout
= {0};
1782 if (term
->dead
|| term
->cmdfd
< 0) return 0;
1784 FD_SET(term
->cmdfd
, &rfd
);
1785 if (select(term
->cmdfd
+1, &rfd
, NULL
, NULL
, &timeout
) < 0) {
1786 if (errno
== EINTR
) continue;
1787 die("select failed: %s", SERRNO
);
1789 if (FD_ISSET(term
->cmdfd
, &rfd
)) return 1;
1796 static int ttycanwrite (void) {
1799 struct timeval timeout
= {0};
1801 if (term
->dead
|| term
->cmdfd
< 0) return 0;
1803 FD_SET(term
->cmdfd
, &wfd
);
1804 if (select(term
->cmdfd
+1, NULL
, &wfd
, NULL
, &timeout
) < 0) {
1805 if (errno
== EINTR
) continue;
1806 die("select failed: %s", SERRNO
);
1808 if (FD_ISSET(term
->cmdfd
, &wfd
)) return 1;
1816 static void wrstr (const char *s
, int len
) {
1817 if (s
== NULL
) return;
1819 unsigned char c
= (unsigned char)(*s
++);
1821 if (c
< 32) fprintf(stderr
, "{%u}", c
); else fwrite(&c
, 1, 1, stderr
);
1827 static void ttyread (void) {
1831 /* append read bytes to unprocessed bytes */
1832 if (term
== NULL
|| term
->dead
|| term
->cmdfd
< 0) return;
1833 #ifdef DUMP_PROG_OUTPUT
1834 term
->xobuflen
= term
->obuflen
;
1836 left
= OBUFSIZ
-term
->obuflen
;
1837 //dlogf("0: ttyread before: free=%d, used=%d", left, term->obuflen);
1838 while (left
> 0 && ttycanread()) {
1841 //if ((ret = read(term->cmdfd, term->obuf+term->obuflen, 1)) < 0) die("Couldn't read from shell: %s", SERRNO);
1842 if ((ret
= read(term
->cmdfd
, term
->obuf
+term
->obuflen
, left
)) < 0) {
1843 //fprintf(stderr, "Warning: couldn't read from tty #%d: %s\n", termidx, SERRNO);
1846 term
->obuflen
+= ret
;
1849 //dlogf("1: ttyread after: free=%d, used=%d", left, term->obuflen);
1850 /* process every complete utf8 char */
1851 #ifdef DUMP_PROG_OUTPUT
1853 FILE *fo
= fopen("zlogo.log", "ab");
1855 fwrite(term
->obuf
+term
->xobuflen
, term
->obuflen
-term
->xobuflen
, 1, fo
);
1861 if (needConversion
) {
1862 // need conversion from locale to utf-8
1863 //fprintf(stderr, "buf: %d bytes\n", term->obuflen);
1864 while (term
->obuflen
> 0) {
1865 char obuf
[UTF_SIZ
+1];
1868 len
= loc2utf(obuf
, ptr
, 1);
1871 fprintf(stderr
, "rdc: [");
1873 fprintf(stderr
, "] --> [");
1875 fprintf(stderr
, "]\n");
1888 // don't do any conversion
1889 while (term
->obuflen
>= UTF_SIZ
|| isfullutf8(ptr
, term
->obuflen
)) {
1892 int charsize
= utf8decode(ptr
, &utf8c
); /* returns size of utf8 char in bytes */
1895 len
= utf8encode(&utf8c
, s
);
1898 fprintf(stderr
, "rdx: [");
1900 fprintf(stderr
, "]\n");
1909 term
->obuflen
-= charsize
;
1911 //dlogf("2: ttyread afterproc: used=%d", term->obuflen);
1913 /* keep any uncomplete utf8 char for the next call */
1914 if (term
->obuflen
> 0) memmove(term
->obuf
, ptr
, term
->obuflen
);
1918 static void ttyflushwrbuf (void) {
1919 if (term
== NULL
|| term
->dead
|| term
->cmdfd
< 0) return;
1920 if (term
->wrbufpos
>= term
->wrbufused
) {
1921 term
->wrbufpos
= term
->wrbufused
= 0;
1924 //dlogf("0: ttyflushwrbuf before: towrite=%d", term->wrbufused-term->wrbufpos);
1925 while (term
->wrbufpos
< term
->wrbufused
&& ttycanwrite()) {
1928 if ((ret
= write(term
->cmdfd
, term
->wrbuf
+term
->wrbufpos
, term
->wrbufused
-term
->wrbufpos
)) == -1) {
1929 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
1931 term
->wrbufpos
+= ret
;
1933 if (term
->wrbufpos
> 0) {
1934 int left
= term
->wrbufused
-term
->wrbufpos
;
1937 // write buffer is empty
1938 term
->wrbufpos
= term
->wrbufused
= 0;
1940 memmove(term
->wrbuf
, term
->wrbuf
+term
->wrbufpos
, left
);
1942 term
->wrbufused
= left
;
1945 //dlogf("1: ttyflushwrbuf after: towrite=%d", term->wrbufused-term->wrbufpos);
1949 // convert char to locale and write it
1950 static void ttywriterawchar (const char *s
, int len
) {
1954 if (s
== NULL
|| len
< 1) return;
1955 clen
= utf2loc(loc
, s
, len
);
1956 if (clen
< 1) return;
1957 #ifdef DUMP_IO_WRITE
1959 fprintf(stderr
, "wrc: [");
1961 fprintf(stderr
, "] --> [");
1963 fprintf(stderr
, "]\n");
1968 while (term
->wrbufused
+clen
>= term
->wrbufsize
) {
1969 //FIXME: make write buffer dynamic?
1970 // force write at least one char
1971 //dlogf("ttywrite: forced write");
1972 if (write(term
->cmdfd
, term
->wrbuf
+term
->wrbufpos
, 1) == -1) {
1973 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
1977 ttyflushwrbuf(); // make room for char
1979 memcpy(term
->wrbuf
+term
->wrbufused
, loc
, clen
);
1980 term
->wrbufused
+= clen
;
1984 static void ttywrite (const char *s
, size_t n
) {
1985 if (term
== NULL
|| term
->dead
|| term
->cmdfd
< 0) return;
1986 #ifdef DUMP_PROG_INPUT
1987 if (s
!= NULL
&& n
> 0) {
1988 FILE *fo
= fopen("zlogw.log", "ab");
1990 fwrite(s
, n
, 1, fo
);
1996 if (s
!= NULL
&& n
> 0) {
1998 unsigned char c
= (unsigned char)(s
[0]);
2000 if (term
->ubufpos
> 0 && isfullutf8(term
->ubuf
, term
->ubufpos
)) {
2001 // have complete char
2002 ttywriterawchar(term
->ubuf
, term
->ubufpos
);
2007 if (term
->ubufpos
== 0) {
2010 ttywriterawchar(s
, 1);
2011 } else if ((c
&0xc0) == 0xc0) {
2013 term
->ubuf
[term
->ubufpos
++] = *s
;
2015 // ignore unsynced utf-8
2022 if (c
< 128 || term
->ubufpos
>= UTF_SIZ
|| (c
&0xc0) == 0xc0) {
2023 // discard previous utf-8, it's bad
2028 term
->ubuf
[term
->ubufpos
++] = *s
;
2031 if (isfullutf8(term
->ubuf
, term
->ubufpos
)) {
2032 // have complete char
2033 ttywriterawchar(term
->ubuf
, term
->ubufpos
);
2042 ////////////////////////////////////////////////////////////////////////////////
2044 static void ttyresize (void) {
2047 if (term
!= NULL
&& term
->cmdfd
>= 0) {
2048 w
.ws_row
= term
->row
;
2049 w
.ws_col
= term
->col
;
2050 w
.ws_xpixel
= w
.ws_ypixel
= 0;
2051 if (ioctl(term
->cmdfd
, TIOCSWINSZ
, &w
) < 0) fprintf(stderr
, "Warning: couldn't set window size: %s\n", SERRNO
);
2057 ////////////////////////////////////////////////////////////////////////////////
2059 static void csidump (void) {
2061 for (int f
= 1; f
< term
->escseq
.len
; ++f
) {
2062 uint c
= (term
->escseq
.buf
[f
]&0xff);
2064 if (isprint(c
)) putchar(c
);
2065 else if (c
== '\n') printf("(\\n)");
2066 else if (c
== '\r') printf("(\\r)");
2067 else if (c
== 0x1b) printf("(\\e)");
2068 else printf("(%02x)", c
);
2074 static void tsetdirt (int top
, int bot
) {
2075 LIMIT(top
, 0, term
->row
-1);
2076 LIMIT(bot
, 0, term
->row
-1);
2077 for (int y
= top
; y
<= bot
; ++y
) markDirty(y
, 2);
2081 static void tfulldirt (void) {
2082 tsetdirt(0, term
->row
-1);
2086 static void tmoveto (int x
, int y
) {
2087 LIMIT(x
, 0, term
->col
-1);
2088 LIMIT(y
, 0, term
->row
-1);
2089 term
->c
.state
&= ~CURSOR_WRAPNEXT
;
2090 if (term
->c
.x
!= x
|| term
->c
.y
!= y
) {
2098 static void tclearregion (int x1
, int y1
, int x2
, int y2
) {
2101 if (x1
> x2
) { temp
= x1
; x1
= x2
; x2
= temp
; }
2102 if (y1
> y2
) { temp
= y1
; y1
= y2
; y2
= temp
; }
2103 LIMIT(x1
, 0, term
->col
-1);
2104 LIMIT(x2
, 0, term
->col
-1);
2105 LIMIT(y1
, 0, term
->row
-1);
2106 LIMIT(y2
, 0, term
->row
-1);
2107 for (int y
= y1
; y
<= y2
; ++y
) {
2108 markDirty(y
, (x1
<= 0 && x2
>= term
->col
-1) ? 2 : 1);
2109 for (int x
= x1
; x
<= x2
; ++x
) {
2110 term
->line
[y
][x
].fg
= term
->c
.attr
.fg
;
2111 term
->line
[y
][x
].bg
= term
->c
.attr
.bg
;
2112 term
->line
[y
][x
].state
= GLYPH_DIRTY
;
2113 term
->line
[y
][x
].mode
= ATTR_NULL
|(term
->c
.attr
.mode
&(ATTR_DEFFG
|ATTR_DEFBG
));
2114 term
->line
[y
][x
].c
[0] = ' ';
2120 static void tcursor (int mode
) {
2121 if (mode
== CURSOR_SAVE
) {
2122 term
->csaved
= term
->c
;
2123 } else if (mode
== CURSOR_LOAD
) {
2124 term
->c
= term
->csaved
;
2125 tmoveto(term
->c
.x
, term
->c
.y
);
2131 static void treset (void) {
2134 term
->c
= (TCursor
){{
2135 .mode
= ATTR_NULL
|ATTR_DEFFG
|ATTR_DEFBG
,
2138 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
2139 term
->c
.attr
.fg
= term
->deffg
;
2140 term
->c
.attr
.bg
= term
->defbg
;
2142 g
.state
= GLYPH_DIRTY
;
2143 g
.mode
= term
->c
.attr
.mode
;
2144 g
.fg
= term
->c
.attr
.fg
;
2145 g
.bg
= term
->c
.attr
.bg
;
2150 term
->bot
= term
->row
-1;
2151 term
->mode
= MODE_WRAP
| MODE_MOUSEBTN
;
2152 //tclearregion(0, 0, term->col-1, term->row-1);
2153 for (int y
= 0; y
< term
->row
; ++y
) {
2155 for (int x
= 0; x
< term
->col
; ++x
) term
->alt
[y
][x
] = term
->line
[y
][x
] = g
;
2157 for (int y
= term
->row
; y
< term
->linecount
; ++y
) {
2158 for (int x
= 0; x
< term
->col
; ++x
) term
->line
[y
][x
] = g
;
2160 tcursor(CURSOR_SAVE
);
2165 static int tinitialize (int col
, int row
) {
2166 //memset(term, 0, sizeof(Term));
2167 term
->wrbufsize
= WBUFSIZ
;
2168 term
->deffg
= term
->deffg
;
2169 term
->defbg
= term
->defbg
;
2172 term
->dirty
= calloc(term
->row
, sizeof(*term
->dirty
));
2173 term
->maxhistory
= opt_maxhistory
;
2174 term
->linecount
= term
->maxhistory
+term
->row
;
2175 term
->line
= calloc(term
->linecount
, sizeof(Line
));
2176 term
->alt
= calloc(term
->row
, sizeof(Line
));
2177 for (int y
= 0; y
< term
->linecount
; ++y
) term
->line
[y
] = calloc(term
->col
, sizeof(Glyph
));
2178 for (int y
= 0; y
< term
->row
; ++y
) term
->alt
[y
] = calloc(term
->col
, sizeof(Glyph
));
2185 static void tswapscreen (void) {
2186 for (int f
= 0; f
< term
->row
; ++f
) {
2187 Line t
= term
->line
[f
];
2189 term
->line
[f
] = term
->alt
[f
];
2192 term
->mode
^= MODE_ALTSCREEN
;
2197 //FIXME: will not work for history
2198 static void selscroll (int orig
, int n
) {
2199 if (term
->sel
.bx
== -1) return;
2201 if (BETWEEN(term
->sel
.by
, orig
, term
->bot
) || BETWEEN(term
->sel
.ey
, orig
, term
->bot
)) {
2202 if ((term
->sel
.by
+= n
) > term
->bot
|| (term
->sel
.ey
+= n
) < term
->top
) {
2206 if (term
->sel
.by
< term
->top
) {
2207 term
->sel
.by
= term
->top
;
2210 if (term
->sel
.ey
> term
->bot
) {
2211 term
->sel
.ey
= term
->bot
;
2212 term
->sel
.ex
= term
->col
;
2214 term
->sel
.b
.y
= term
->sel
.by
, term
->sel
.b
.x
= term
->sel
.bx
;
2215 term
->sel
.e
.y
= term
->sel
.ey
, term
->sel
.e
.x
= term
->sel
.ex
;
2220 static void tscrolldown (int orig
, int n
) {
2223 LIMIT(n
, 0, term
->bot
-orig
+1);
2224 //fprintf(stderr, "tscrolldown(%d, %d)\n", orig, n);
2225 tclearregion(0, term
->bot
-n
+1, term
->col
-1, term
->bot
);
2226 for (int f
= term
->bot
; f
>= orig
+n
; --f
) {
2227 temp
= term
->line
[f
];
2228 term
->line
[f
] = term
->line
[f
-n
];
2229 term
->line
[f
-n
] = temp
;
2237 static void tscrollup (int orig
, int n
) {
2240 if (term
== NULL
) return;
2241 LIMIT(n
, 0, term
->bot
-orig
+1);
2243 //fprintf(stderr, "tscrollup(%d, %d)\n", orig, n);
2244 if (n
== 1 && orig
== 0 && term
->bot
>= term
->row
-1 && term
->maxhistory
> 0) {
2245 Line l
= term
->line
[term
->linecount
-1];
2247 for (int f
= term
->linecount
-1; f
> term
->row
; --f
) term
->line
[f
] = term
->line
[f
-1];
2248 term
->line
[term
->row
] = l
;
2249 for (int x
= 0; x
< term
->col
; ++x
) l
[x
] = term
->line
[0][x
];
2252 tclearregion(0, orig
, term
->col
-1, orig
+n
-1);
2254 for (int f
= orig
; f
<= term
->bot
-n
; ++f
) {
2255 temp
= term
->line
[f
];
2256 term
->line
[f
] = term
->line
[f
+n
];
2257 term
->line
[f
+n
] = temp
;
2261 selscroll(orig
, -n
);
2265 static void tnewline (int first_col
) {
2268 if (y
== term
->bot
) tscrollup(term
->top
, 1); else ++y
;
2269 tmoveto(first_col
? 0 : term
->c
.x
, y
);
2273 static void csiparse (void) {
2274 const char *p
= term
->escseq
.buf
;
2276 term
->escseq
.narg
= 0;
2277 if (*p
== '?') { term
->escseq
.priv
= 1; ++p
; }
2278 while (p
< term
->escseq
.buf
+term
->escseq
.len
) {
2279 int n
= term
->escseq
.arg
[term
->escseq
.narg
];
2281 for (; *p
&& isdigit(*p
); ++p
) n
= n
*10+(p
[0]-'0');
2282 term
->escseq
.arg
[term
->escseq
.narg
] = n
;
2284 if (*p
== ';' && term
->escseq
.narg
+1 < ESC_ARG_SIZ
) {
2285 ++term
->escseq
.narg
;
2288 term
->escseq
.mode
= *p
;
2289 ++term
->escseq
.narg
;
2296 static void tsetchar (const char *c
) {
2298 int rev
= 0, gfx
= 0;
2300 if (!needConversion
&& unimap
!= NULL
) {
2304 if (cc
>= 0 && cc
<= 65535) {
2305 uchar uc
= unimap
[cc
];
2326 if (rev || gfx || (term->line[term->c.y][term->c.x].state & GLYPH_SET) == 0 || ATTRCMP(term->line[term->c.y][term->c.x], term->c.attr)) {
2327 term->line[term->c.y][term->c.x] = term->c.attr;
2328 if (rev) term->line[term->c.y][term->c.x].mode ^= ATTR_REVERSE;
2329 if (gfx) term->line[term->c.y][term->c.x].mode |= ATTR_GFX;
2331 int clen = utf8size(c), olen = utf8size(term->line[term->c.y][term->c.x].c);
2333 if (clen < 1 || (clen == olen && memcmp(c, term->line[term->c.y][term->c.x].c, clen) == 0)) return;
2336 term
->line
[term
->c
.y
][term
->c
.x
] = term
->c
.attr
;
2337 if (rev
) term
->line
[term
->c
.y
][term
->c
.x
].mode
^= ATTR_REVERSE
;
2339 if (term
->line
[term
->c
.y
][term
->c
.x
].mode
&ATTR_G1
) {
2340 term
->line
[term
->c
.y
][term
->c
.x
].mode
&= ~ATTR_GFX
;
2341 term
->line
[term
->c
.y
][term
->c
.x
].mode
|= ATTR_GFX1
;
2343 term
->line
[term
->c
.y
][term
->c
.x
].mode
|= ATTR_GFX
;
2344 term
->line
[term
->c
.y
][term
->c
.x
].mode
&= ~ATTR_GFX1
;
2348 markDirty(term
->c
.y
, 1);
2349 memcpy(term
->line
[term
->c
.y
][term
->c
.x
].c
, c
, UTF_SIZ
);
2350 if (IS_GFX(term
->line
[term
->c
.y
][term
->c
.x
].mode
)) {
2351 unsigned char c
= (unsigned char)(term
->line
[term
->c
.y
][term
->c
.x
].c
[0]);
2353 if (c
> 95 && c
< 128) term
->line
[term
->c
.y
][term
->c
.x
].c
[0] -= 95;
2354 else if (c
> 127) term
->line
[term
->c
.y
][term
->c
.x
].c
[0] = ' ';
2356 term
->line
[term
->c
.y
][term
->c
.x
].state
= (GLYPH_SET
| GLYPH_DIRTY
);
2360 static void tfillscreenwithE (void) {
2361 for (int y
= 0; y
< term
->row
; ++y
) {
2363 for (int x
= 0; x
< term
->col
; ++x
) {
2364 term
->line
[y
][x
].state
= /*GLYPH_SET |*/ GLYPH_DIRTY
;
2365 term
->line
[y
][x
].c
[0] = 'E';
2366 term
->line
[y
][x
].mode
= ATTR_NULL
;
2372 static void tdeletechar (int n
) {
2373 int src
= term
->c
.x
+n
;
2374 int dst
= term
->c
.x
;
2375 int size
= term
->col
-src
;
2377 markDirty(term
->c
.y
, 2);
2378 if (src
>= term
->col
) {
2379 tclearregion(term
->c
.x
, term
->c
.y
, term
->col
-1, term
->c
.y
);
2381 memmove(&term
->line
[term
->c
.y
][dst
], &term
->line
[term
->c
.y
][src
], size
*sizeof(Glyph
));
2382 tclearregion(term
->col
-n
, term
->c
.y
, term
->col
-1, term
->c
.y
);
2387 static void tinsertblank (int n
) {
2388 int src
= term
->c
.x
;
2390 int size
= term
->col
-dst
;
2392 markDirty(term
->c
.y
, 2);
2393 if (dst
>= term
->col
) {
2394 tclearregion(term
->c
.x
, term
->c
.y
, term
->col
-1, term
->c
.y
);
2396 memmove(&term
->line
[term
->c
.y
][dst
], &term
->line
[term
->c
.y
][src
], size
*sizeof(Glyph
));
2397 tclearregion(src
, term
->c
.y
, dst
-1, term
->c
.y
);
2402 static void tinsertblankline (int n
) {
2403 if (term
->c
.y
< term
->top
|| term
->c
.y
> term
->bot
) return;
2404 tscrolldown(term
->c
.y
, n
);
2408 static void tdeleteline (int n
) {
2409 if (term
->c
.y
< term
->top
|| term
->c
.y
> term
->bot
) return;
2410 tscrollup(term
->c
.y
, n
);
2414 static void tsetattr (int *attr
, int l
) {
2415 for (int f
= 0; f
< l
; ++f
) {
2418 term
->c
.attr
.mode
&= ~(ATTR_REVERSE
| ATTR_UNDERLINE
| ATTR_BOLD
);
2419 term
->c
.attr
.mode
|= ATTR_DEFFG
| ATTR_DEFBG
;
2420 term
->c
.attr
.fg
= term
->deffg
;
2421 term
->c
.attr
.bg
= term
->defbg
;
2424 term
->c
.attr
.mode
|= ATTR_BOLD
;
2427 term
->c
.attr
.mode
|= ATTR_UNDERLINE
;
2430 term
->c
.attr
.mode
|= ATTR_REVERSE
;
2433 term
->c
.attr
.mode
&= ~ATTR_BOLD
;
2436 term
->c
.attr
.mode
&= ~ATTR_UNDERLINE
;
2439 term
->c
.attr
.mode
&= ~ATTR_REVERSE
;
2442 if (f
+2 < l
&& attr
[f
+1] == 5) {
2444 if (BETWEEN(attr
[f
], 0, 255)) {
2445 term
->c
.attr
.fg
= attr
[f
];
2446 term
->c
.attr
.mode
&= ~ATTR_DEFFG
;
2448 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[f
]);
2451 //fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2452 term
->c
.attr
.fg
= term
->deffg
;
2453 term
->c
.attr
.mode
|= ATTR_DEFFG
;
2457 term
->c
.attr
.fg
= term
->deffg
;
2458 term
->c
.attr
.mode
|= ATTR_DEFFG
;
2461 if (f
+2 < l
&& attr
[f
+1] == 5) {
2463 if (BETWEEN(attr
[f
], 0, 255)) {
2464 term
->c
.attr
.bg
= attr
[f
];
2465 term
->c
.attr
.mode
&= ~ATTR_DEFBG
;
2467 fprintf(stderr
, "erresc: bad bgcolor %d\n", attr
[f
]);
2470 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[f
]);
2474 term
->c
.attr
.bg
= term
->defbg
;
2475 term
->c
.attr
.mode
|= ATTR_DEFBG
;
2478 if (BETWEEN(attr
[f
], 30, 37)) { term
->c
.attr
.fg
= attr
[f
]-30; term
->c
.attr
.mode
&= ~ATTR_DEFFG
; }
2479 else if (BETWEEN(attr
[f
], 40, 47)) { term
->c
.attr
.bg
= attr
[f
]-40; term
->c
.attr
.mode
&= ~ATTR_DEFBG
; }
2480 else if (BETWEEN(attr
[f
], 90, 97)) { term
->c
.attr
.fg
= attr
[f
]-90+8; term
->c
.attr
.mode
&= ~ATTR_DEFFG
; }
2481 else if (BETWEEN(attr
[f
], 100, 107)) { term
->c
.attr
.fg
= attr
[f
]-100+8; term
->c
.attr
.mode
&= ~ATTR_DEFFG
; }
2482 else { fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[f
]); csidump(); }
2489 static void tsetscroll (int t
, int b
) {
2492 LIMIT(t
, 0, term
->row
-1);
2493 LIMIT(b
, 0, term
->row
-1);
2504 ////////////////////////////////////////////////////////////////////////////////
2506 static void csihandle (void) {
2507 switch (term
->escseq
.mode
) {
2508 case '@': /* ICH -- Insert <n> blank char */
2509 DEFAULT(term
->escseq
.arg
[0], 1);
2510 tinsertblank(term
->escseq
.arg
[0]);
2512 case 'A': /* CUU -- Cursor <n> Up */
2514 DEFAULT(term
->escseq
.arg
[0], 1);
2515 tmoveto(term
->c
.x
, term
->c
.y
-term
->escseq
.arg
[0]);
2517 case 'B': /* CUD -- Cursor <n> Down */
2518 DEFAULT(term
->escseq
.arg
[0], 1);
2519 tmoveto(term
->c
.x
, term
->c
.y
+term
->escseq
.arg
[0]);
2521 case 'C': /* CUF -- Cursor <n> Forward */
2523 DEFAULT(term
->escseq
.arg
[0], 1);
2524 tmoveto(term
->c
.x
+term
->escseq
.arg
[0], term
->c
.y
);
2526 case 'D': /* CUB -- Cursor <n> Backward */
2527 DEFAULT(term
->escseq
.arg
[0], 1);
2528 tmoveto(term
->c
.x
-term
->escseq
.arg
[0], term
->c
.y
);
2530 case 'E': /* CNL -- Cursor <n> Down and first col */
2531 DEFAULT(term
->escseq
.arg
[0], 1);
2532 tmoveto(0, term
->c
.y
+term
->escseq
.arg
[0]);
2534 case 'F': /* CPL -- Cursor <n> Up and first col */
2535 DEFAULT(term
->escseq
.arg
[0], 1);
2536 tmoveto(0, term
->c
.y
-term
->escseq
.arg
[0]);
2538 case 'G': /* CHA -- Move to <col> */
2539 case '`': /* XXX: HPA -- same? */
2540 DEFAULT(term
->escseq
.arg
[0], 1);
2541 tmoveto(term
->escseq
.arg
[0]-1, term
->c
.y
);
2543 case 'H': /* CUP -- Move to <row> <col> */
2544 case 'f': /* XXX: HVP -- same? */
2545 DEFAULT(term
->escseq
.arg
[0], 1);
2546 DEFAULT(term
->escseq
.arg
[1], 1);
2547 tmoveto(term
->escseq
.arg
[1]-1, term
->escseq
.arg
[0]-1);
2549 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
2550 case 'J': /* ED -- Clear screen */
2552 switch (term
->escseq
.arg
[0]) {
2554 tclearregion(term
->c
.x
, term
->c
.y
, term
->col
-1, term
->c
.y
);
2555 if (term
->c
.y
< term
->row
-1) tclearregion(0, term
->c
.y
+1, term
->col
-1, term
->row
-1);
2558 if (term
->c
.y
> 1) tclearregion(0, 0, term
->col
-1, term
->c
.y
-1);
2559 tclearregion(0, term
->c
.y
, term
->c
.x
, term
->c
.y
);
2562 tclearregion(0, 0, term
->col
-1, term
->row
-1);
2568 case 'K': /* EL -- Clear line */
2569 switch (term
->escseq
.arg
[0]) {
2571 tclearregion(term
->c
.x
, term
->c
.y
, term
->col
-1, term
->c
.y
);
2574 tclearregion(0, term
->c
.y
, term
->c
.x
, term
->c
.y
);
2577 tclearregion(0, term
->c
.y
, term
->col
-1, term
->c
.y
);
2581 case 'S': /* SU -- Scroll <n> line up */
2582 DEFAULT(term
->escseq
.arg
[0], 1);
2583 tscrollup(term
->top
, term
->escseq
.arg
[0]);
2585 case 'T': /* SD -- Scroll <n> line down */
2586 DEFAULT(term
->escseq
.arg
[0], 1);
2587 tscrolldown(term
->top
, term
->escseq
.arg
[0]);
2589 case 'L': /* IL -- Insert <n> blank lines */
2590 DEFAULT(term
->escseq
.arg
[0], 1);
2591 tinsertblankline(term
->escseq
.arg
[0]);
2593 case 'l': /* RM -- Reset Mode */
2594 if (term
->escseq
.priv
) {
2595 switch (term
->escseq
.arg
[0]) {
2596 case 1: // 1001 for xterm compatibility
2597 DUMP_KEYPAD_SWITCH("1", "OFF");
2598 term
->mode
&= ~MODE_APPKEYPAD
;
2600 case 5: /* DECSCNM -- Remove reverse video */
2601 if (IS_SET(MODE_REVERSE
)) {
2602 term
->mode
&= ~MODE_REVERSE
;
2606 case 7: /* autowrap off */
2607 term
->mode
&= ~MODE_WRAP
;
2609 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
2611 case 20: /* non-standard code? */
2612 term
->mode
&= ~MODE_CRLF
;
2614 case 25: /* hide cursor */
2615 term
->c
.state
|= CURSOR_HIDE
;
2617 case 1000: /* disable X11 xterm mouse reporting */
2618 term
->mode
&= ~MODE_MOUSEBTN
;
2621 term
->mode
&= ~MODE_MOUSEMOTION
;
2623 case 1049: /* = 1047 and 1048 */
2626 if (IS_SET(MODE_ALTSCREEN
)) {
2627 tclearregion(0, 0, term
->col
-1, term
->row
-1);
2630 if (term
->escseq
.arg
[0] != 1049) break;
2632 tcursor(CURSOR_LOAD
);
2638 switch (term
->escseq
.arg
[0]) {
2640 term
->mode
&= ~MODE_INSERT
;
2647 case 'M': /* DL -- Delete <n> lines */
2648 DEFAULT(term
->escseq
.arg
[0], 1);
2649 tdeleteline(term
->escseq
.arg
[0]);
2651 case 'X': /* ECH -- Erase <n> char */
2652 DEFAULT(term
->escseq
.arg
[0], 1);
2653 tclearregion(term
->c
.x
, term
->c
.y
, term
->c
.x
+ term
->escseq
.arg
[0], term
->c
.y
);
2655 case 'P': /* DCH -- Delete <n> char */
2656 DEFAULT(term
->escseq
.arg
[0], 1);
2657 tdeletechar(term
->escseq
.arg
[0]);
2659 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
2660 case 'd': /* VPA -- Move to <row> */
2661 DEFAULT(term
->escseq
.arg
[0], 1);
2662 tmoveto(term
->c
.x
, term
->escseq
.arg
[0]-1);
2664 case 'h': /* SM -- Set terminal mode */
2665 if (term
->escseq
.priv
) {
2666 switch (term
->escseq
.arg
[0]) {
2668 DUMP_KEYPAD_SWITCH("1", "ON");
2669 term
->mode
|= MODE_APPKEYPAD
;
2671 case 5: /* DECSCNM -- Reverve video */
2672 if (!IS_SET(MODE_REVERSE
)) {
2673 term
->mode
|= MODE_REVERSE
;
2678 term
->mode
|= MODE_WRAP
;
2681 term
->mode
|= MODE_CRLF
;
2683 case 12: /* att610 -- Start blinking cursor (IGNORED) */
2684 /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
2685 if (term
->escseq
.narg
> 1 && term
->escseq
.arg
[1] != 25) break;
2687 term
->c
.state
&= ~CURSOR_HIDE
;
2689 case 1000: /* 1000,1002: enable xterm mouse report */
2690 term
->mode
|= MODE_MOUSEBTN
;
2693 term
->mode
|= MODE_MOUSEMOTION
;
2695 case 1049: /* = 1047 and 1048 */
2698 if (IS_SET(MODE_ALTSCREEN
)) tclearregion(0, 0, term
->col
-1, term
->row
-1); else tswapscreen();
2699 if (term
->escseq
.arg
[0] != 1049) break;
2701 tcursor(CURSOR_SAVE
);
2703 default: goto unknown
;
2706 switch (term
->escseq
.arg
[0]) {
2708 term
->mode
|= MODE_INSERT
;
2715 case 'm': /* SGR -- Terminal attribute (color) */
2716 tsetattr(term
->escseq
.arg
, term
->escseq
.narg
);
2718 case 'r': /* DECSTBM -- Set Scrolling Region */
2719 if (term
->escseq
.priv
&& term
->escseq
.arg
[0] == 1001) {
2720 // xterm compatibility
2721 DUMP_KEYPAD_SWITCH("1001", "OFF");
2722 term
->mode
&= ~MODE_APPKEYPAD
;
2723 } else if (term
->escseq
.priv
) {
2726 DEFAULT(term
->escseq
.arg
[0], 1);
2727 DEFAULT(term
->escseq
.arg
[1], term
->row
);
2728 tsetscroll(term
->escseq
.arg
[0]-1, term
->escseq
.arg
[1]-1);
2732 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
2733 if (term
->escseq
.priv
&& term
->escseq
.arg
[0] == 1001) {
2734 // xterm compatibility
2735 DUMP_KEYPAD_SWITCH("1001", "ON");
2736 term
->mode
|= MODE_APPKEYPAD
;
2738 tcursor(CURSOR_SAVE
);
2741 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
2742 tcursor(CURSOR_LOAD
);
2746 fprintf(stderr
, "erresc: unknown csi ");
2753 static void csireset (void) {
2754 memset(&term
->escseq
, 0, sizeof(term
->escseq
));
2758 static void tputtab (void) {
2759 int space
= opt_tabsize
-term
->c
.x
%opt_tabsize
;
2761 if (space
> 0) tmoveto(term
->c
.x
+space
, term
->c
.y
);
2765 ////////////////////////////////////////////////////////////////////////////////
2766 // put char to output buffer or process command
2767 static void tputc (const char *c
) {
2770 if (term
->esc
& ESC_START
) {
2771 if (term
->esc
& ESC_CSI
) {
2772 term
->escseq
.buf
[term
->escseq
.len
++] = ascii
;
2773 if (BETWEEN(ascii
, 0x40, 0x7E) || term
->escseq
.len
>= ESC_BUF_SIZ
) {
2778 } else if (term
->esc
& ESC_OSC
) {
2779 /* TODO: handle other OSC */
2782 term
->esc
= ESC_START
| ESC_TITLE
;
2785 } else if (term
->esc
& ESC_TITLE
) {
2786 if (ascii
== '\a' || term
->titlelen
+1 >= ESC_TITLE_SIZ
) {
2788 term
->title
[term
->titlelen
] = '\0';
2789 fixWindowTitle(term
);
2792 int len
= utf8size(c
);
2794 if (len
> 0 && term
->titlelen
+len
< ESC_TITLE_SIZ
) {
2795 memcpy(term
->title
+term
->titlelen
, c
, len
);
2796 term
->titlelen
+= len
;
2798 term
->title
[term
->titlelen
] = '\0';
2800 } else if (term
->esc
& ESC_ALTCHARSET
) {
2802 case '0': /* Line drawing crap */
2803 term
->c
.attr
.mode
|= ATTR_GFX
;
2805 case 'B': /* Back to regular text */
2806 term
->c
.attr
.mode
&= ~ATTR_GFX
;
2809 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2810 term
->c
.attr
.mode
&= ~ATTR_GFX
;
2814 } else if (term
->esc
& ESC_ALTG1
) {
2816 case '0': /* Line drawing crap */
2817 term
->c
.attr
.mode
|= ATTR_GFX1
;
2819 case 'B': /* Back to regular text */
2820 term
->c
.attr
.mode
&= ~ATTR_GFX1
;
2823 fprintf(stderr
, "esc unhandled charset: ESC ) %c\n", ascii
);
2824 term
->c
.attr
.mode
&= ~ATTR_GFX1
;
2828 } else if (term
->esc
& ESC_HASH
) {
2830 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
2835 } else if (term
->esc
& ESC_PERCENT
) {
2839 case '[': term
->esc
|= ESC_CSI
; break;
2840 case ']': term
->esc
|= ESC_OSC
; break;
2841 case '(': term
->esc
|= ESC_ALTCHARSET
; break;
2842 case ')': term
->esc
|= ESC_ALTG1
; break;
2843 case '#': term
->esc
|= ESC_HASH
; break;
2844 case '%': term
->esc
|= ESC_PERCENT
; break;
2845 case 'D': /* IND -- Linefeed */
2847 if (term
->c
.y
== term
->bot
) tscrollup(term
->top
, 1); else tmoveto(term
->c
.x
, term
->c
.y
+1);
2849 case 'E': /* NEL -- Next line */
2851 tnewline(1); /* always go to first col */
2853 case 'M': /* RI -- Reverse linefeed */
2855 if (term
->c
.y
== term
->top
) tscrolldown(term
->top
, 1); else tmoveto(term
->c
.x
, term
->c
.y
-1);
2857 case 'c': /* RIS -- Reset to inital state */
2861 case '=': /* DECPAM -- Application keypad */
2862 DUMP_KEYPAD_SWITCH("=", "ON");
2864 term
->mode
|= MODE_APPKEYPAD
;
2866 case '>': /* DECPNM -- Normal keypad */
2867 DUMP_KEYPAD_SWITCH(">", "OFF");
2869 term
->mode
&= ~MODE_APPKEYPAD
;
2871 case '7': /* DECSC -- Save Cursor */
2872 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
2875 tcursor(CURSOR_SAVE
);
2877 case '8': /* DECRC -- Restore Cursor */
2880 tcursor(CURSOR_LOAD
);
2882 case 'Z': /* DEC private identification */
2884 ttywritestr("\x1b[?1;2c");
2888 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X ('%c')\n", (uchar
)ascii
, isprint(ascii
)?ascii
:'.');
2893 if (term
->sel
.bx
!= -1 && BETWEEN(term
->c
.y
, term
->sel
.by
, term
->sel
.ey
)) term
->sel
.bx
= -1;
2895 case '\t': tputtab(); break;
2896 case '\b': tmoveto(term
->c
.x
-1, term
->c
.y
); break;
2897 case '\r': tmoveto(0, term
->c
.y
); break;
2898 case '\v': tnewline(0); break;
2899 case '\f': case '\n': tnewline(IS_SET(MODE_CRLF
)); break; /* go to first col if the mode is set */
2900 case '\a': if (!(xw
.state
& WIN_FOCUSED
)) xseturgency(1); XBell(xw
.dpy
, 100); break;
2901 case 14: term
->c
.attr
.mode
|= ATTR_G1
; break;
2902 case 15: term
->c
.attr
.mode
&= ~ATTR_G1
; break;
2903 case '\033': csireset(); term
->esc
= ESC_START
; break;
2905 if (needConversion
&& IS_GFX(term
->c
.attr
.mode
)) {
2909 if (cc
< 32 || cc
>= 127) break; //FIXME: nothing at all
2911 if ((unsigned char)ascii
< 32 || ascii
== 127) break; // seems that this chars are empty too
2913 if (IS_SET(MODE_WRAP
) && (term
->c
.state
&CURSOR_WRAPNEXT
)) tnewline(1); /* always go to first col */
2915 if (term
->c
.x
+1 < term
->col
) tmoveto(term
->c
.x
+1, term
->c
.y
); else term
->c
.state
|= CURSOR_WRAPNEXT
;
2922 ////////////////////////////////////////////////////////////////////////////////
2924 static int tresize (int col
, int row
) {
2925 int mincol
= MIN(col
, term
->col
);
2926 int slide
= term
->c
.y
-row
+1;
2929 if (col
< 1 || row
< 1) return 0;
2931 g
.state
= GLYPH_DIRTY
;
2932 g
.mode
= ATTR_NULL
|ATTR_DEFFG
|ATTR_DEFBG
;
2939 tsetscroll(0, term
->row
-1);
2940 for (; slide
> 0; --slide
) tscrollup(0, 1); // to fill history
2943 if (row
< term
->row
) {
2944 /* free unneeded rows */
2945 for (int f
= row
; f
< term
->row
; ++f
) free(term
->alt
[f
]);
2946 for (int f
= term
->linecount
-(term
->row
-row
); f
< term
->linecount
; ++f
) free(term
->line
[f
]);
2947 term
->linecount
-= (term
->row
-row
);
2948 /* resize to new height */
2949 term
->alt
= realloc(term
->alt
, row
*sizeof(Line
));
2950 term
->line
= realloc(term
->line
, term
->linecount
*sizeof(Line
));
2951 } else if (row
> term
->row
) {
2952 /* resize to new height */
2953 term
->alt
= realloc(term
->alt
, row
*sizeof(Line
));
2954 term
->line
= realloc(term
->line
, (row
+term
->maxhistory
)*sizeof(Line
));
2955 /* add more lines */
2956 for (int f
= term
->row
; f
< row
; ++f
) {
2957 term
->alt
[f
] = calloc(col
, sizeof(Glyph
));
2958 for (int x
= 0; x
< col
; ++x
) term
->alt
[f
][x
] = g
;
2960 for (int f
= 0; f
< row
-term
->row
; ++f
) {
2961 int y
= term
->linecount
++;
2963 term
->line
[y
] = calloc(col
, sizeof(Glyph
));
2964 for (int x
= 0; x
< col
; ++x
) term
->line
[y
][x
] = g
;
2968 if (row
!= term
->row
) {
2969 term
->dirty
= realloc(term
->dirty
, row
*sizeof(*term
->dirty
));
2972 /* resize each row to new width, zero-pad if needed */
2973 for (int f
= 0; f
< term
->linecount
; ++f
) {
2974 term
->line
[f
] = realloc(term
->line
[f
], col
*sizeof(Glyph
));
2975 for (int x
= mincol
; x
< col
; ++x
) term
->line
[f
][x
] = g
;
2978 term
->alt
[f
] = realloc(term
->alt
[f
], col
*sizeof(Glyph
));
2979 for (int x
= mincol
; x
< col
; ++x
) term
->alt
[f
][x
] = g
;
2982 /* update terminal size */
2986 /* make use of the LIMIT in tmoveto */
2987 tmoveto(term
->c
.x
, term
->c
.y
);
2988 /* reset scrolling region */
2989 tsetscroll(0, row
-1);
2995 static void xresize (int col
, int row
) {
2999 if (term
== NULL
) return;
3000 oldw
= term
->picbufw
;
3001 oldh
= term
->picbufh
;
3002 term
->picbufw
= MAX(1, col
*xw
.cw
);
3003 term
->picbufh
= MAX(1, row
*xw
.ch
);
3004 newbuf
= XCreatePixmap(xw
.dpy
, xw
.win
, term
->picbufw
, term
->picbufh
, XDefaultDepth(xw
.dpy
, xw
.scr
));
3005 XCopyArea(xw
.dpy
, term
->picbuf
, newbuf
, dc
.gc
, 0, 0, term
->picbufw
, term
->picbufh
, 0, 0);
3006 XFreePixmap(xw
.dpy
, term
->picbuf
);
3007 XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[term
->defbg
]);
3008 if (term
->picbufw
> oldw
) {
3009 XFillRectangle(xw
.dpy
, newbuf
, dc
.gc
, oldw
, 0, term
->picbufw
-oldw
, MIN(term
->picbufh
, oldh
));
3010 } else if (term
->picbufw
< oldw
&& (opt_border
> 0 || xw
.w
> term
->picbufw
)) {
3011 XClearArea(xw
.dpy
, xw
.win
, opt_border
+term
->picbufw
, opt_border
, xw
.w
-term
->picbufh
-opt_border
, opt_border
+MIN(term
->picbufh
, oldh
), False
);
3013 if (term
->picbufh
> oldh
) {
3014 XFillRectangle(xw
.dpy
, newbuf
, dc
.gc
, 0, oldh
, term
->picbufw
, term
->picbufh
-oldh
);
3015 } else if (term
->picbufh
< oldh
&& (opt_border
> 0 || xw
.tabheight
> 0 || xw
.h
> term
->picbufh
)) {
3016 XClearArea(xw
.dpy
, xw
.win
, opt_border
, opt_border
+term
->picbufh
, xw
.w
-2*opt_border
, xw
.h
-term
->picbufh
-opt_border
-xw
.tabheight
, False
);
3018 term
->picbuf
= newbuf
;
3024 ////////////////////////////////////////////////////////////////////////////////
3025 // x11 drawing and utils
3026 static void xloadcols (void) {
3029 ulong white
= WhitePixel(xw
.dpy
, xw
.scr
);
3031 dc
.col
= calloc(MAX_COLOR
+1, sizeof(dc
.col
[0]));
3032 for (f
= 0; f
<= MAX_COLOR
; ++f
) dc
.col
[f
] = white
;
3033 /* load colors [0-15] */
3034 for (f
= 0; f
<= 15; ++f
) {
3035 const char *cname
= opt_colornames
[f
]!=NULL
?opt_colornames
[f
]:defcolornames
[f
];
3037 if (!XAllocNamedColor(xw
.dpy
, xw
.cmap
, cname
, &color
, &color
)) {
3038 fprintf(stderr
, "WARNING: could not allocate color #%d: '%s'\n", f
, cname
);
3040 dc
.col
[f
] = color
.pixel
;
3043 /* load colors [256-...] */
3044 for (f
= 256; f
<= MAX_COLOR
; ++f
) {
3045 const char *cname
= opt_colornames
[f
];
3047 if (cname
== NULL
) {
3048 if (LEN(defextcolornames
) <= f
-256) continue;
3049 cname
= defextcolornames
[f
-256];
3051 if (cname
== NULL
) continue;
3052 if (!XAllocNamedColor(xw
.dpy
, xw
.cmap
, cname
, &color
, &color
)) {
3053 fprintf(stderr
, "WARNING: could not allocate color #%d: '%s'\n", f
, cname
);
3055 dc
.col
[f
] = color
.pixel
;
3058 /* load colors [16-255] ; same colors as xterm */
3059 for (f
= 16, r
= 0; r
< 6; ++r
) {
3060 for (g
= 0; g
< 6; ++g
) {
3061 for (b
= 0; b
< 6; ++b
) {
3062 if (opt_colornames
[f
] != NULL
) {
3063 if (!XAllocNamedColor(xw
.dpy
, xw
.cmap
, opt_colornames
[f
], &color
, &color
)) {
3064 fprintf(stderr
, "WARNING: could not allocate color #%d: '%s'\n", f
, opt_colornames
[f
]);
3066 dc
.col
[f
] = color
.pixel
;
3069 color
.red
= r
== 0 ? 0 : 0x3737+0x2828*r
;
3070 color
.green
= g
== 0 ? 0 : 0x3737+0x2828*g
;
3071 color
.blue
= b
== 0 ? 0 : 0x3737+0x2828*b
;
3072 if (!XAllocColor(xw
.dpy
, xw
.cmap
, &color
)) {
3073 fprintf(stderr
, "WARNING: could not allocate color #%d\n", f
);
3075 dc
.col
[f
] = color
.pixel
;
3082 for (r
= 0; r
< 24; ++r
, ++f
) {
3083 if (opt_colornames
[f
] != NULL
) {
3084 if (!XAllocNamedColor(xw
.dpy
, xw
.cmap
, opt_colornames
[f
], &color
, &color
)) {
3085 fprintf(stderr
, "WARNING: could not allocate color #%d: '%s'\n", f
, opt_colornames
[f
]);
3087 dc
.col
[f
] = color
.pixel
;
3090 color
.red
= color
.green
= color
.blue
= 0x0808+0x0a0a*r
;
3091 if (!XAllocColor(xw
.dpy
, xw
.cmap
, &color
)) {
3092 fprintf(stderr
, "WARNING: could not allocate color #%d\n", f
);
3094 dc
.col
[f
] = color
.pixel
;
3099 for (int f
= 0; f
< LEN(opt_colornames
); ++f
) if (opt_colornames
[f
]) free(opt_colornames
[f
]);
3103 static void xclear (int x1
, int y1
, int x2
, int y2
) {
3104 XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[IS_SET(MODE_REVERSE
) ? term
->deffg
: term
->defbg
]);
3105 XFillRectangle(xw
.dpy
, term
->picbuf
, dc
.gc
, x1
*xw
.cw
, y1
*xw
.ch
, (x2
-x1
+1)*xw
.cw
, (y2
-y1
+1)*xw
.ch
);
3109 static void xhints (void) {
3110 XClassHint
class = {opt_class
, opt_title
};
3111 XWMHints wm
= {.flags
= InputHint
, .input
= 1};
3113 .flags
= PSize
| PResizeInc
| PBaseSize
,
3116 .height_inc
= xw
.ch
,
3118 .base_height
= 2*opt_border
+xw
.tabheight
,
3119 .base_width
= 2*opt_border
,
3121 XSetWMProperties(xw
.dpy
, xw
.win
, NULL
, NULL
, NULL
, 0, &size
, &wm
, &class);
3125 static XFontSet
xinitfont (const char *fontstr
) {
3127 char *def
, **missing
;
3131 set
= XCreateFontSet(xw
.dpy
, fontstr
, &missing
, &n
, &def
);
3133 while (n
--) fprintf(stderr
, "sterm: missing fontset: %s\n", missing
[n
]);
3134 XFreeStringList(missing
);
3140 static void xgetfontinfo (XFontSet set
, int *ascent
, int *descent
, short *lbearing
, short *rbearing
) {
3141 XFontStruct
**xfonts
;
3145 *ascent
= *descent
= *lbearing
= *rbearing
= 0;
3146 n
= XFontsOfFontSet(set
, &xfonts
, &font_names
);
3147 for (int f
= 0; f
< n
; ++f
) {
3148 *ascent
= MAX(*ascent
, (*xfonts
)->ascent
);
3149 *descent
= MAX(*descent
, (*xfonts
)->descent
);
3150 *lbearing
= MAX(*lbearing
, (*xfonts
)->min_bounds
.lbearing
);
3151 *rbearing
= MAX(*rbearing
, (*xfonts
)->max_bounds
.rbearing
);
3157 static void initfonts (const char *fontstr
, const char *bfontstr
, const char *tabfont
) {
3158 if ((dc
.font
[0].set
= xinitfont(fontstr
)) == NULL
) die("can't load font %s", fontstr
);
3159 xgetfontinfo(dc
.font
[0].set
, &dc
.font
[0].ascent
, &dc
.font
[0].descent
, &dc
.font
[0].lbearing
, &dc
.font
[0].rbearing
);
3161 if ((dc
.font
[1].set
= xinitfont(bfontstr
)) == NULL
) die("can't load font %s", bfontstr
);
3162 xgetfontinfo(dc
.font
[1].set
, &dc
.font
[1].ascent
, &dc
.font
[1].descent
, &dc
.font
[1].lbearing
, &dc
.font
[1].rbearing
);
3164 if ((dc
.font
[2].set
= xinitfont(tabfont
)) == NULL
) die("can't load font %s", tabfont
);
3165 xgetfontinfo(dc
.font
[2].set
, &dc
.font
[2].ascent
, &dc
.font
[2].descent
, &dc
.font
[2].lbearing
, &dc
.font
[2].rbearing
);
3169 static void xinit (void) {
3170 XSetWindowAttributes attrs
;
3174 if (!(xw
.dpy
= XOpenDisplay(NULL
))) die("can't open display");
3176 XA_VT_SELECTION
= XInternAtom(xw
.dpy
, "_STERM_SELECTION_", 0);
3177 XA_CLIPBOARD
= XInternAtom(xw
.dpy
, "CLIPBOARD", 0);
3178 XA_UTF8
= XInternAtom(xw
.dpy
, "UTF8_STRING", 0);
3179 XA_NETWM_NAME
= XInternAtom(xw
.dpy
, "_NET_WM_NAME", 0);
3180 XA_TARGETS
= XInternAtom(xw
.dpy
, "TARGETS", 0);
3182 xw
.scr
= XDefaultScreen(xw
.dpy
);
3184 initfonts(opt_fontnorm
, opt_fontbold
, opt_fonttab
);
3185 /* XXX: Assuming same size for bold font */
3186 xw
.cw
= dc
.font
[0].rbearing
-dc
.font
[0].lbearing
;
3187 xw
.ch
= dc
.font
[0].ascent
+dc
.font
[0].descent
;
3188 xw
.tch
= dc
.font
[2].ascent
+dc
.font
[2].descent
;
3189 xw
.tabheight
= xw
.tch
+2;
3190 //fprintf(stderr, "tabh: %d\n", xw.tabheight);
3193 xw
.cmap
= XDefaultColormap(xw
.dpy
, xw
.scr
);
3195 /* window - default size */
3196 term
->picbufh
= term
->row
*xw
.ch
;
3197 term
->picbufw
= term
->col
*xw
.cw
;
3199 xw
.h
= term
->picbufh
+2*opt_border
+xw
.tabheight
;
3200 xw
.w
= term
->picbufw
+2*opt_border
;
3202 attrs
.background_pixel
= dc
.col
[defaultBG
];
3203 attrs
.border_pixel
= dc
.col
[defaultBG
];
3204 attrs
.bit_gravity
= NorthWestGravity
;
3205 attrs
.event_mask
= FocusChangeMask
| KeyPressMask
3206 | ExposureMask
| VisibilityChangeMask
| StructureNotifyMask
3207 | ButtonMotionMask
| ButtonPressMask
| ButtonReleaseMask
3208 | EnterWindowMask
| LeaveWindowMask
;
3209 attrs
.colormap
= xw
.cmap
;
3210 //fprintf(stderr, "oe: [%s]\n", opt_embed);
3211 parent
= opt_embed
? strtol(opt_embed
, NULL
, 0) : XRootWindow(xw
.dpy
, xw
.scr
);
3212 xw
.win
= XCreateWindow(xw
.dpy
, parent
, 0, 0,
3213 xw
.w
, xw
.h
, 0, XDefaultDepth(xw
.dpy
, xw
.scr
), InputOutput
,
3214 XDefaultVisual(xw
.dpy
, xw
.scr
),
3215 CWBackPixel
| CWBorderPixel
| CWBitGravity
| CWEventMask
3218 term
->picbuf
= XCreatePixmap(xw
.dpy
, xw
.win
, term
->picbufw
, term
->picbufh
, XDefaultDepth(xw
.dpy
, xw
.scr
));
3219 xw
.pictab
= XCreatePixmap(xw
.dpy
, xw
.win
, xw
.w
, xw
.tabheight
>0?xw
.tabheight
:1, XDefaultDepth(xw
.dpy
, xw
.scr
));
3221 xw
.xim
= XOpenIM(xw
.dpy
, NULL
, NULL
, NULL
);
3222 xw
.xic
= XCreateIC(xw
.xim
, XNInputStyle
, XIMPreeditNothing
| XIMStatusNothing
, XNClientWindow
, xw
.win
, XNFocusWindow
, xw
.win
, NULL
);
3224 dc
.gc
= XCreateGC(xw
.dpy
, xw
.win
, 0, NULL
);
3225 /* white cursor, black outline */
3226 cursor
= XCreateFontCursor(xw
.dpy
, XC_xterm
);
3227 XDefineCursor(xw
.dpy
, xw
.win
, cursor
);
3228 XRecolorCursor(xw
.dpy
, cursor
,
3229 &(XColor
){.red
= 0xffff, .green
= 0xffff, .blue
= 0xffff},
3230 &(XColor
){.red
= 0x0000, .green
= 0x0000, .blue
= 0x0000});
3231 xw
.xembed
= XInternAtom(xw
.dpy
, "_XEMBED", False
);
3232 fixWindowTitle(term
);
3233 //XStoreName(xw.dpy, xw.win, opt_title);
3235 XSetForeground(xw
.dpy
, dc
.gc
, 0);
3236 XFillRectangle(xw
.dpy
, term
->picbuf
, dc
.gc
, 0, 0, term
->picbufw
, term
->picbufh
);
3237 if (xw
.tabheight
> 0) XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, 0, 0, xw
.w
, xw
.tabheight
);
3239 XMapWindow(xw
.dpy
, xw
.win
);
3245 static void xdraws (const char *s
, const Glyph
*base
, int x
, int y
, int charlen
, int bytelen
) {
3246 int fg
= base
->fg
, bg
= base
->bg
, temp
;
3247 int winx
= x
*xw
.cw
, winy
= y
*xw
.ch
+dc
.font
[0].ascent
, width
= charlen
*xw
.cw
;
3248 XFontSet fontset
= dc
.font
[0].set
;
3249 int defF
= base
->mode
&ATTR_DEFFG
, defB
= base
->mode
&ATTR_DEFBG
;
3251 /* only switch default fg/bg if term is in RV mode */
3252 if (IS_SET(MODE_REVERSE
)) {
3253 if (defF
) fg
= term
->defbg
;
3254 if (defB
) bg
= term
->deffg
;
3256 if (base
->mode
&ATTR_REVERSE
) defF
= defB
= 0;
3257 if (base
->mode
& ATTR_BOLD
) {
3258 if (defF
&& defB
&& defaultBoldFG
>= 0) fg
= defaultBoldFG
;
3259 else if (fg
< 8) fg
+= 8;
3260 fontset
= dc
.font
[1].set
;
3262 if (base
->mode
& ATTR_UNDERLINE
&& defaultUnderlineFG
>= 0) {
3263 if (defF
&& defB
) fg
= defaultUnderlineFG
;
3266 if (base
->mode
&ATTR_REVERSE
) { temp
= fg
; fg
= bg
; bg
= temp
; }
3268 XSetBackground(xw
.dpy
, dc
.gc
, dc
.col
[bg
]);
3269 XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[fg
]);
3271 if (IS_GFX(base
->mode
)) {
3272 XmbDrawImageString(xw
.dpy
, term
->picbuf
, fontset
, dc
.gc
, winx
, winy
, s
, bytelen
);
3273 } else if (!needConversion
) {
3274 XmbDrawImageString(xw
.dpy
, term
->picbuf
, fontset
, dc
.gc
, winx
, winy
, s
, bytelen
);
3277 //k8: dunno why, but Xutf8DrawImageString() ignores ascii (at least for terminus)
3278 const char *pos
= s
;
3281 while (pos
< s
+bytelen
) {
3285 if ((unsigned char)(pos
[0]) < 128) {
3286 for (e
= pos
+1; e
< s
+bytelen
&& (unsigned char)(*e
) < 128; ++e
) ;
3288 XmbDrawImageString(xw
.dpy
, term
->picbuf
, fontset
, dc
.gc
, xpos
, winy
, pos
, e
-pos
);
3290 for (clen
= 0, e
= pos
; e
< s
+bytelen
&& (unsigned char)(*e
) >= 128; ++e
) {
3291 if (((unsigned char)(e
[0])&0xc0) == 0xc0) ++clen
;
3293 Xutf8DrawImageString(xw
.dpy
, term
->picbuf
, fontset
, dc
.gc
, xpos
, winy
, pos
, e
-pos
);
3301 if (base
->mode
& ATTR_UNDERLINE
) {
3302 XDrawLine(xw
.dpy
, term
->picbuf
, dc
.gc
, winx
, winy
+1, winx
+width
-1, winy
+1);
3307 /* copy buffer pixmap to screen pixmap */
3308 static void xcopy (int x
, int y
, int cols
, int rows
) {
3309 int src_x
= x
*xw
.cw
, src_y
= y
*xw
.ch
, src_w
= cols
*xw
.cw
, src_h
= rows
*xw
.ch
;
3310 int dst_x
= opt_border
+src_x
, dst_y
= opt_border
+src_y
;
3312 XCopyArea(xw
.dpy
, term
->picbuf
, xw
.win
, dc
.gc
, src_x
, src_y
, src_w
, src_h
, dst_x
, dst_y
);
3316 static void xdrawcursor (void) {
3320 if (term
== NULL
) return;
3322 LIMIT(term
->oldcx
, 0, term
->col
-1);
3323 LIMIT(term
->oldcy
, 0, term
->row
-1);
3326 scry
= term
->oldcy
+term
->topline
;
3327 if (scry
< term
->row
&&
3328 (term
->oldcy
!= term
->c
.y
|| term
->oldcx
!= term
->c
.x
|| (term
->c
.state
&CURSOR_HIDE
) || !(xw
.state
& WIN_FOCUSED
))) {
3329 /* remove the old cursor */
3330 sl
= utf8size(term
->line
[term
->oldcy
][scrx
].c
);
3331 xdraws(term
->line
[term
->oldcy
][scrx
].c
, &term
->line
[term
->oldcy
][scrx
], scrx
, scry
, 1, sl
);
3332 //xclear(scrx, term->oldcy, scrx, term->oldcy);
3333 xcopy(scrx
, scry
, 1, 1);
3335 /* draw the new one */
3336 if (!(term
->c
.state
&CURSOR_HIDE
)) {
3340 scry
= term
->c
.y
+term
->topline
;
3341 if (scry
< term
->row
) {
3342 if (!(xw
.state
& WIN_FOCUSED
)) {
3343 if (defaultCursorInactiveBG
< 0) {
3344 XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[defaultCursorBG
]);
3345 XDrawRectangle(xw
.dpy
, term
->picbuf
, dc
.gc
, scrx
*xw
.cw
, scry
*xw
.ch
, xw
.cw
-1, xw
.ch
-1);
3348 g
.bg
= defaultCursorInactiveBG
;
3349 g
.fg
= defaultCursorInactiveFG
;
3351 g
.fg
= defaultCursorFG
;
3352 g
.bg
= defaultCursorBG
;
3354 memcpy(g
.c
, term
->line
[term
->c
.y
][scrx
].c
, UTF_SIZ
);
3357 if (IS_SET(MODE_REVERSE
)) g
.mode
|= ATTR_REVERSE
;
3359 xdraws(g
.c
, &g
, scrx
, scry
, 1, sl
);
3361 term
->oldcy
= term
->c
.y
;
3364 xcopy(scrx
, scry
, 1, 1);
3369 #define TAB_VISIBLE (6)
3371 static void xdrawTabBar (void) {
3372 if (xw
.tabheight
> 0 && updateTabBar
) {
3373 static int tableft
= 0;
3375 int tabw
= xw
.w
/TAB_VISIBLE
;
3376 XFontSet fontset
= dc
.font
[2].set
;
3378 XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[normalTabBG
]);
3379 XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, 0, 0, xw
.w
, xw
.tabheight
);
3381 if (tableft
+TAB_VISIBLE
-1 > termidx
) tableft
= termidx
-TAB_VISIBLE
;
3382 else if (termidx
< tableft
) tableft
= termidx
;
3383 if (tableft
< 0) tableft
= 0;
3385 for (int f
= tabstart
; f
< TAB_VISIBLE
; ++f
) {
3386 int x
= (f
-tabstart
)*tabw
;;
3390 if (f
>= term_count
) {
3391 XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[normalTabBG
]);
3392 XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, x
, 0, xw
.w
, xw
.tabheight
);
3395 title
= term_array
[f
]->title
;
3396 if (!title
[0]) title
= opt_title
;
3397 tit
= SPrintf("[%d]%s", f
, title
);
3400 XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[f
== termidx
? activeTabBG
: normalTabBG
]);
3401 XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, x
, 0, tabw
, xw
.tabheight
);
3403 XSetBackground(xw
.dpy
, dc
.gc
, dc
.col
[f
== termidx
? activeTabBG
: normalTabBG
]);
3404 XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[f
== termidx
? activeTabFG
: normalTabFG
]);
3406 if (needConversion
) {
3409 while (*title
&& xx
< x
+tabw
) {
3410 const char *e
= title
;
3413 memset(&r
, 0, sizeof(r
));
3415 if ((unsigned char)(*e
) > 127) {
3416 while (*e
&& (unsigned char)(*e
) > 127) ++e
;
3417 Xutf8DrawImageString(xw
.dpy
, xw
.pictab
, fontset
, dc
.gc
, xx
, dc
.font
[2].ascent
+2, title
, e
-title
);
3418 Xutf8TextExtents(fontset
, title
, e
-title
, &r
, NULL
);
3420 while (*e
&& (unsigned char)(*e
) <= 127) ++e
;
3421 XmbDrawImageString(xw
.dpy
, xw
.pictab
, fontset
, dc
.gc
, xx
, dc
.font
[2].ascent
+2, title
, e
-title
);
3422 XmbTextExtents(fontset
, title
, e
-title
, &r
, NULL
);
3428 XmbDrawImageString(xw
.dpy
, xw
.pictab
, fontset
, dc
.gc
, x
+2, dc
.font
[2].ascent
+2, title
, strlen(title
));
3430 Xutf8DrawImageString(xw
.dpy
, xw
.pictab
, fontset
, dc
.gc
, x
+2, dc
.font
[2].ascent
+2, title
, strlen(title
));
3434 XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[f
== termidx
? activeTabBG
: normalTabBG
]);
3435 XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, x
+tabw
-2, 0, 2, xw
.tabheight
);
3437 XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[normalTabFG
]);
3438 XDrawLine(xw
.dpy
, xw
.pictab
, dc
.gc
, x
+tabw
-1, 0, x
+tabw
-1, xw
.tabheight
);
3441 XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[normalTabFG
]);
3442 //XDrawRectangle(xw.dpy, xw.pictab, dc.gc, -1, 0, xw.w+1, xw.tabheight+1);
3443 XDrawLine(xw
.dpy
, xw
.pictab
, dc
.gc
, 0, 0, xw
.w
, 0);
3445 XCopyArea(xw
.dpy
, xw
.pictab
, xw
.win
, dc
.gc
, 0, 0, xw
.w
, xw
.tabheight
, 0, xw
.h
-xw
.tabheight
);
3450 static void drawline (int x1
, int x2
, int scry
, int lineno
) {
3455 if (scry
< 0 || scry
>= term
->row
) return;
3456 if (lineno
< 0 || lineno
>= term
->linecount
) {
3457 xclear(0, scry
, term
->col
-1, scry
);
3458 xcopy(0, scry
, term
->col
, 1);
3460 if (lineno
< term
->row
&& term
->topline
== 0) {
3461 //if (term->topline != 0) term->dirty[lineno] = 2;
3462 if (!term
->dirty
[lineno
]) return;
3463 // fix 'dirty' flag for line
3464 if (term
->dirty
[lineno
]&0x02) {
3465 // mark full line as dirty
3466 for (int x
= 0; x
< term
->col
; ++x
) term
->line
[lineno
][x
].state
|= GLYPH_DIRTY
;
3468 // correct 'dirty' flag
3469 term
->dirty
[lineno
] = 0;
3470 if (x1
> 0) for (int x
= 0; x
< x1
; ++x
) if (term
->line
[lineno
][x
].state
&GLYPH_DIRTY
) { term
->dirty
[lineno
] = 1; break; }
3471 if (!term
->dirty
[lineno
] && x2
< term
->col
) for (int x
= x2
; x
< term
->col
; ++x
) if (term
->line
[lineno
][x
].state
&GLYPH_DIRTY
) { term
->dirty
[lineno
] = 1; break; }
3473 // find dirty region
3474 for (stx
= x1
; stx
< x2
; ++stx
) if (term
->line
[lineno
][stx
].state
&GLYPH_DIRTY
) break;
3475 for (ex
= x2
; ex
> stx
; --ex
) if (term
->line
[lineno
][ex
-1].state
&GLYPH_DIRTY
) break;
3476 if (stx
>= x2
|| ex
<= stx
) return; // nothing to do
3482 base
= term
->line
[lineno
][stx
];
3485 //xclear(stx, scry, ex-1, scry); //k8: actually, we don't need this for good monospace fonts, so...
3486 for (int x
= stx
; x
< ex
; ++x
) {
3487 new = term
->line
[lineno
][x
];
3488 term
->line
[lineno
][x
].state
&= ~GLYPH_DIRTY
; //!
3489 if (term
->sel
.bx
!= -1 && new.c
[0] && selected(x
, lineno
)) new.mode
^= ATTR_REVERSE
;
3490 if (ib
> 0 && (ATTRCMP(base
, new) || ib
>= DRAW_BUF_SIZ
-UTF_SIZ
)) {
3491 // flush draw buffer
3492 xdraws(term
->drawbuf
, &base
, ox
, scry
, ic
, ib
);
3495 if (ib
== 0) { ox
= x
; base
= new; }
3496 sl
= utf8size(new.c
);
3497 memcpy(term
->drawbuf
+ib
, new.c
, sl
);
3501 if (ib
> 0) xdraws(term
->drawbuf
, &base
, ox
, scry
, ic
, ib
);
3502 //xcopy(0, scry, term->col, 1);
3503 if (term
->c
.y
== lineno
&& term
->c
.x
>= stx
&& term
->c
.x
< ex
) xdrawcursor();
3504 xcopy(stx
, scry
, ex
-stx
, 1);
3509 static void drawregion (int x1
, int y1
, int x2
, int y2
, int forced
) {
3510 if (!forced
&& !(xw
.state
& WIN_VISIBLE
)) return;
3512 if (term
->topline
< term
->row
) {
3513 for (int y
= y1
; y
< y2
; ++y
) drawline(x1
, x2
, y
+term
->topline
, y
);
3515 if (term
->topline
> 0) {
3516 int scry
= MIN(term
->topline
, term
->row
), y
= term
->row
;
3518 if (term
->topline
>= term
->row
) y
+= term
->topline
-term
->row
;
3519 while (--scry
>= 0) {
3520 drawline(0, term
->col
, scry
, y
);
3530 static void draw (int forced
) {
3532 drawregion(0, 0, term
->col
, term
->row
, forced
);
3533 term
->lastDrawTime
= mclock_ticks();
3538 static void expose (XEvent
*ev
) {
3539 XExposeEvent
*e
= &ev
->xexpose
;
3541 if (xw
.state
&WIN_REDRAW
) {
3542 if (!e
->count
&& term
!= NULL
) {
3543 xw
.state
&= ~WIN_REDRAW
;
3544 xcopy(0, 0, term
->col
, term
->row
);
3546 } else if (term
!= NULL
) {
3547 XCopyArea(xw
.dpy
, term
->picbuf
, xw
.win
, dc
.gc
, e
->x
-opt_border
, e
->y
-opt_border
, e
->width
, e
->height
, e
->x
, e
->y
);
3554 static void visibility (XEvent
*ev
) {
3555 XVisibilityEvent
*e
= &ev
->xvisibility
;
3557 if (e
->state
== VisibilityFullyObscured
) xw
.state
&= ~WIN_VISIBLE
;
3558 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 */
3562 static void unmap (XEvent
*ev
) {
3563 xw
.state
&= ~WIN_VISIBLE
;
3567 static void xseturgency (int add
) {
3568 XWMHints
*h
= XGetWMHints(xw
.dpy
, xw
.win
);
3570 h
->flags
= add
? (h
->flags
| XUrgencyHint
) : (h
->flags
& ~XUrgencyHint
);
3571 XSetWMHints(xw
.dpy
, xw
.win
, h
);
3576 static void focus (XEvent
*ev
) {
3577 if (ev
->type
== FocusIn
) {
3578 xw
.state
|= WIN_FOCUSED
;
3581 xw
.state
&= ~WIN_FOCUSED
;
3586 xcopy(0, 0, term
->col
, term
->row
);
3590 ////////////////////////////////////////////////////////////////////////////////
3592 static const char *kmap (KeySym k
, uint state
) {
3593 const char *res
= NULL
;
3594 state
&= ~Mod2Mask
; // numlock
3595 for (int f
= 0; f
< keymap_used
; ++f
) {
3596 uint mask
= keymap
[f
].mask
;
3598 if (keymap
[f
].key
== k
&& ((state
&mask
) == mask
|| (mask
== XK_NO_MOD
&& !state
))) {
3599 //fprintf(stderr, "kp: %d\n", IS_SET(MODE_APPKEYPAD));
3600 if (!IS_SET(MODE_APPKEYPAD
)) {
3601 if (!keymap
[f
].kp
) {
3602 //fprintf(stderr, "nokp! %d %s\n", keymap[f].kp, keymap[f].str+1);
3603 return keymap
[f
].str
; // non-keypad hit
3607 //fprintf(stderr, "kp! %d %s\n", keymap[f].kp, keymap[f].str+1);
3608 if (keymap
[f
].kp
) return keymap
[f
].str
; // keypad hit
3609 res
= keymap
[f
].str
; // kp mode, but non-kp mapping found
3616 static const char *kbind (KeySym k
, uint state
) {
3617 state
&= ~Mod2Mask
; // numlock
3618 for (int f
= 0; f
< keybinds_used
; ++f
) {
3619 uint mask
= keybinds
[f
].mask
;
3621 if (keybinds
[f
].key
== k
&& ((state
&mask
) == mask
|| (mask
== XK_NO_MOD
&& !state
))) return keybinds
[f
].str
;
3627 static KeySym
do_keytrans (KeySym ks
) {
3628 for (int f
= 0; f
< keytrans_used
; ++f
) if (keytrans
[f
].src
== ks
) return keytrans
[f
].dst
;
3633 static void kpress (XEvent
*ev
) {
3634 XKeyEvent
*e
= &ev
->xkey
;
3635 KeySym ksym
= NoSymbol
;
3641 if (term
== NULL
) return;
3642 //if (len < 1) len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
3643 if ((len
= Xutf8LookupString(xw
.xic
, e
, buf
, sizeof(buf
), &ksym
, &status
)) > 0) buf
[len
] = 0;
3644 // leave only known mods
3645 e
->state
&= (Mod1Mask
| Mod4Mask
| ControlMask
| ShiftMask
);
3648 const char *ksname
= XKeysymToString(ksym
);
3650 fprintf(stderr
, "utf(%d):[%s] (%s) 0x%08x\n", len
, len
>=0?buf
:"<shit>", ksname
, (unsigned int)e
->state
);
3653 if ((kstr
= kbind(ksym
, e
->state
)) != NULL
) {
3655 executeCommands(kstr
);
3659 if ((kstr
= kmap(do_keytrans(ksym
), e
->state
)) != NULL
) {
3660 if (kstr
[0]) ttywritestr(kstr
);
3662 int meta
= (e
->state
&Mod1Mask
);
3664 int shift = (e->state&ShiftMask);
3665 int ctrl = (e->state&ControlMask);
3670 ttywritestr("\x1b\x0a");
3672 if (IS_SET(MODE_CRLF
)) ttywritestr("\r\n"); else ttywritestr("\r");
3677 if (meta
&& len
== 1) ttywritestr("\x1b");
3686 ////////////////////////////////////////////////////////////////////////////////
3688 static void cmessage (XEvent
*e
) {
3689 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
3690 if (e
->xclient
.message_type
== xw
.xembed
&& e
->xclient
.format
== 32) {
3691 if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_IN
) {
3692 xw
.state
|= WIN_FOCUSED
;
3694 } else if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_OUT
) {
3695 xw
.state
&= ~WIN_FOCUSED
;
3702 ////////////////////////////////////////////////////////////////////////////////
3703 static void resize (XEvent
*e
) {
3707 if (e
->xconfigure
.width
== xw
.w
&& e
->xconfigure
.height
== xw
.h
) return;
3708 xw
.w
= e
->xconfigure
.width
;
3709 xw
.h
= e
->xconfigure
.height
;
3710 col
= (xw
.w
-2*opt_border
)/xw
.cw
;
3711 row
= (xw
.h
-2*opt_border
-xw
.tabheight
)/xw
.ch
;
3712 if (col
== term
->col
&& row
== term
->row
) return;
3713 for (int f
= 0; f
< term_count
; ++f
) {
3714 term
= term_array
[f
];
3715 if (tresize(col
, row
) && ot
== term
) draw(1);
3720 XFreePixmap(xw
.dpy
, xw
.pictab
);
3721 xw
.pictab
= XCreatePixmap(xw
.dpy
, xw
.win
, xw
.w
, xw
.tabheight
>0?xw
.tabheight
:1, XDefaultDepth(xw
.dpy
, xw
.scr
));
3726 static inline int last_draw_too_old (void) {
3727 return (mclock_ticks()-term
->lastDrawTime
>= DRAW_TIMEOUT
);
3731 ////////////////////////////////////////////////////////////////////////////////
3733 static void (*handler
[LASTEvent
])(XEvent
*) = {
3734 [KeyPress
] = kpress
,
3735 [ClientMessage
] = cmessage
,
3736 [ConfigureNotify
] = resize
,
3737 [VisibilityNotify
] = visibility
,
3738 [UnmapNotify
] = unmap
,
3742 [MotionNotify
] = bmotion
,
3743 [ButtonPress
] = bpress
,
3744 [ButtonRelease
] = brelease
,
3745 [SelectionNotify
] = selnotify
,
3746 [SelectionRequest
] = selrequest
,
3747 [SelectionClear
] = selclear
,
3751 static void run (void) {
3752 //int stuff_to_print = 0;
3753 int xfd
= XConnectionNumber(xw
.dpy
);
3755 while (term_count
> 0) {
3758 struct timeval timeout
;
3764 //FD_SET(term->cmdfd, &rfd);
3765 // have something to write?
3766 for (int f
= 0; f
< term_count
; ++f
) {
3767 Term
*t
= term_array
[f
];
3769 if (!t
->dead
&& term
->cmdfd
>= 0 && t
->pid
!= 0) {
3770 if (t
->cmdfd
> maxfd
) maxfd
= t
->cmdfd
;
3771 FD_SET(t
->cmdfd
, &rfd
);
3772 if (t
->wrbufpos
< t
->wrbufused
) FD_SET(t
->cmdfd
, &wfd
);
3777 timeout
.tv_usec
= SELECT_TIMEOUT
*1000;
3778 if (select(maxfd
+1, &rfd
, &wfd
, NULL
, &timeout
) < 0) {
3779 if (errno
== EINTR
) continue;
3780 die("select failed: %s", SERRNO
);
3783 for (int f
= 0; f
< term_count
; ++f
) {
3784 Term
*t
= term_array
[f
];
3786 if (!t
->dead
&& term
->cmdfd
>= 0) {
3787 if (FD_ISSET(t
->cmdfd
, &wfd
)) {
3794 if (FD_ISSET(t
->cmdfd
, &rfd
)) {
3800 //t->wantRedraw = 1;
3801 //if (t == term) stuff_to_print = 1;
3807 if (term_count
== 0) break;
3809 if (updateTabBar
|| (term
!= NULL
&& !term
->dead
&& term
->wantRedraw
&& last_draw_too_old())) {
3810 //stuff_to_print = 0;
3811 term
->wantRedraw
= 0;
3815 if (XPending(xw
.dpy
)) {
3816 while (XPending(xw
.dpy
)) {
3817 XNextEvent(xw
.dpy
, &ev
);
3818 if (XFilterEvent(&ev
, xw
.win
)) continue;
3819 if (handler
[ev
.type
]) (handler
[ev
.type
])(&ev
);
3826 ////////////////////////////////////////////////////////////////////////////////
3827 typedef const char * (*IniHandlerFn
) (const char *optname
, const char *fmt
, char *argstr
, void *udata
);
3837 static const char *inifnGenericOneArg (const char *optname
, const char *fmt
, char *argstr
, void *udata
) {
3838 return iniParseArguments(argstr
, fmt
, udata
);
3842 static const char *inifnGenericOneStr (const char *optname
, const char *fmt
, char *argstr
, void *udata
) {
3844 const char *err
= iniParseArguments(argstr
, fmt
, &s
);
3846 if (err
!= NULL
) return err
;
3847 if ((s
= strdup(s
)) == NULL
) return "out of memory";
3849 char **ustr
= (char **)udata
;
3851 if (*ustr
) free(*ustr
);
3858 static const IniCommand iniCommands
[] = {
3859 {"term", "s!-", &opt_term
, inifnGenericOneStr
},
3860 {"class", "s!-", &opt_class
, inifnGenericOneStr
},
3861 {"title", "s!-", &opt_title
, inifnGenericOneStr
},
3862 {"fontnorm", "s!-", &opt_fontnorm
, inifnGenericOneStr
},
3863 {"fontbold", "s!-", &opt_fontbold
, inifnGenericOneStr
},
3864 {"fonttab", "s!-", &opt_fonttab
, inifnGenericOneStr
},
3865 {"shell", "s!-", &opt_shell
, inifnGenericOneStr
},
3866 {"doubleclick_timeout", "i{0,10000}", &opt_doubleclick_timeout
, inifnGenericOneArg
},
3867 {"tripleclick_timeout", "i{0,10000}", &opt_tripleclick_timeout
, inifnGenericOneArg
},
3868 {"tabsize", "i{1,256}", &opt_tabsize
, inifnGenericOneArg
},
3869 {"border", "i{0,256}", &opt_border
, inifnGenericOneArg
},
3870 {"defaultfg", "i{0,511}", &defaultFG
, inifnGenericOneArg
},
3871 {"defaultbg", "i{0,511}", &defaultBG
, inifnGenericOneArg
},
3872 {"defaultcursorfg", "i{0,511}", &defaultCursorFG
, inifnGenericOneArg
},
3873 {"defaultcursorbg", "i{0,511}", &defaultCursorBG
, inifnGenericOneArg
},
3874 {"defaultinactivecursorfg", "i{0,511}", &defaultCursorInactiveFG
, inifnGenericOneArg
},
3875 {"defaultinactivecursorbg", "i{-1,511}", &defaultCursorInactiveBG
, inifnGenericOneArg
},
3876 {"defaultboldfg", "i{-1,511}", &defaultBoldFG
, inifnGenericOneArg
},
3877 {"defaultunderlinefg", "i{-1,511}", &defaultUnderlineFG
, inifnGenericOneArg
},
3878 {"normaltabfg", "i{0,511}", &normalTabFG
, inifnGenericOneArg
},
3879 {"normaltabbg", "i{0,511}", &normalTabBG
, inifnGenericOneArg
},
3880 {"activetabfg", "i{0,511}", &activeTabFG
, inifnGenericOneArg
},
3881 {"activetabbg", "i{0,511}", &activeTabBG
, inifnGenericOneArg
},
3882 {"maxhistory", "i{0,65535}", &opt_maxhistory
, inifnGenericOneArg
},
3883 {NULL
, NULL
, NULL
, NULL
}
3887 #define INI_LINE_SIZE (32768)
3889 // <0: file not found
3890 // >0: file loading error
3892 static int loadConfig (const char *fname
) {
3893 int inifelse
= 0; // 0: not; 1: doing true; 2: doing false; -1: waiting else/endif; -2: waiting endif
3894 FILE *fi
= fopen(fname
, "r");
3895 const char *err
= NULL
;
3899 if (fi
== NULL
) return -1;
3900 if ((line
= malloc(INI_LINE_SIZE
)) == NULL
) { err
= "out of memory"; goto quit
; }
3902 while (fgets(line
, INI_LINE_SIZE
-1, fi
) != NULL
) {
3903 char *optname
, *argstr
;
3907 line
[INI_LINE_SIZE
-1] = 0;
3909 for (optname
= line
; *optname
&& isspace(*optname
); ++optname
) ;
3910 if (!optname
[0] || optname
[0] == '#') continue; // comment
3911 if (!isalnum(optname
[0])) { err
= "invalid option name"; goto quit
; }
3914 if (!argstr
[0] || isspace(argstr
[0])) break;
3915 if (!isalnum(argstr
[0]) && argstr
[0] != '_' && argstr
[0] != '.') { err
= "invalid option name"; goto quit
; }
3916 *argstr
= tolower(*argstr
);
3919 if (*argstr
) *argstr
++ = 0;
3921 if (strcasecmp(optname
, "ifterm") == 0) {
3924 if (inifelse
!= 0) { err
= "nested ifs are not allowed"; goto quit
; }
3926 if ((err
= iniParseArguments(argstr
, "s", &val
)) != NULL
) goto quit
;
3927 if (strcasecmp(opt_term
, val
) == 0) inifelse
= 1;
3930 if (strcasecmp(optname
, "else") == 0) {
3932 case -1: inifelse
= 2; break;
3933 case 2: case -2: case 0: err
= "else without if"; goto quit
;
3934 case 1: inifelse
= -2; break;
3938 if (strcasecmp(optname
, "endif") == 0) {
3940 case -1: case -2: case 1: case 2: inifelse
= 0; break;
3941 case 0: err
= "endif without if"; goto quit
;
3948 //fprintf(stderr, "skip: [%s]\n", argstr);
3951 if (opt_term_locked
&& strcasecmp(optname
, "term") == 0) continue; // termname given in command line
3952 // ok, we have option name in `optname` and arguments in `argstr`
3953 if (strncmp(optname
, "color.", 6) == 0) {
3958 if (!optname
[0]) { err
= "invalid color option"; goto quit
; }
3960 if (!isdigit(*optname
)) { err
= "invalid color option"; goto quit
; }
3961 n
= (n
*10)+(optname
[0]-'0');
3964 if (n
< 0 || n
> 511) { err
= "invalid color index"; goto quit
; }
3966 if ((err
= iniParseArguments(argstr
, "s!-", &s
)) != NULL
) goto quit
;
3967 if ((s
= strdup(s
)) == NULL
) { err
= "out of memory"; goto quit
; }
3968 if (opt_colornames
[n
] != NULL
) free(opt_colornames
[n
]);
3969 opt_colornames
[n
] = s
;
3973 if (strcmp(optname
, "unimap") == 0) {
3977 //unimap 0x2592 0x61 alt
3978 if ((err
= iniParseArguments(argstr
, "i{0,65535}i{0,126}|s!-", &uni
, &ch
, &alt
)) != NULL
) goto quit
;
3979 if (alt
!= NULL
&& strcasecmp(alt
, "alt") != 0) { err
= "invalid unimap"; goto quit
; }
3980 if (unimap
== NULL
) {
3981 if ((unimap
= calloc(65536, sizeof(unimap
[0]))) == NULL
) { err
= "out of memory"; goto quit
; }
3983 if (alt
!= NULL
&& ch
== 0) alt
= NULL
;
3984 if (alt
!= NULL
&& ch
< 96) { err
= "invalid unimap"; goto quit
; }
3986 if (alt
!= NULL
) unimap
[uni
] |= 0x80;
3990 if (strcmp(optname
, "keytrans_reset") == 0) {
3991 if ((err
= iniParseArguments(argstr
, "")) != NULL
) goto quit
;
3995 if (strcmp(optname
, "keytrans") == 0) {
3996 char *src
= NULL
, *dst
= NULL
;
3998 if ((err
= iniParseArguments(argstr
, "s!-s!-", &src
, &dst
)) != NULL
) goto quit
;
3999 keytrans_add(src
, dst
);
4003 if (strcmp(optname
, "keybind_reset") == 0) {
4004 if ((err
= iniParseArguments(argstr
, "")) != NULL
) goto quit
;
4008 if (strcmp(optname
, "keybind") == 0) {
4009 char *key
= NULL
, *act
= NULL
;
4011 if ((err
= iniParseArguments(argstr
, "s!-R!", &key
, &act
)) != NULL
) goto quit
;
4012 keybind_add(key
, act
);
4016 if (strcmp(optname
, "keymap_reset") == 0) {
4017 if ((err
= iniParseArguments(argstr
, "")) != NULL
) goto quit
;
4021 if (strcmp(optname
, "keymap") == 0) {
4022 char *key
= NULL
, *str
= NULL
;
4024 if ((err
= iniParseArguments(argstr
, "s!-s!-", &key
, &str
)) != NULL
) goto quit
;
4025 keymap_add(key
, str
);
4029 for (int f
= 0; iniCommands
[f
].name
!= NULL
; ++f
) {
4030 if (strcmp(iniCommands
[f
].name
, optname
) == 0) {
4031 if ((err
= iniCommands
[f
].fn(optname
, iniCommands
[f
].fmt
, argstr
, iniCommands
[f
].udata
)) != NULL
) goto quit
;
4036 if (!goodoption
) { err
= "unknown option"; goto quit
; }
4039 if (line
!= NULL
) free(line
);
4041 if (err
== NULL
&& inifelse
!= 0) err
= "if without endif";
4042 if (err
!= NULL
) die("ini error at line %d: %s", lineno
, err
);
4047 static void initDefaultOptions (void) {
4048 opt_title
= strdup("sterm");
4049 opt_class
= strdup("sterm");
4050 opt_term
= strdup(TNAME
);
4051 opt_fontnorm
= strdup(FONT
);
4052 opt_fontbold
= strdup(BOLDFONT
);
4053 opt_fonttab
= strdup(FONTTAB
);
4054 opt_shell
= strdup(SHELL
);
4056 memset(opt_colornames
, 0, sizeof(opt_colornames
));
4057 for (int f
= 0; f
< LEN(defcolornames
); ++f
) opt_colornames
[f
] = strdup(defcolornames
[f
]);
4058 for (int f
= 0; f
< LEN(defextcolornames
); ++f
) opt_colornames
[f
+256] = strdup(defextcolornames
[f
]);
4060 keytrans_add("KP_Home", "Home");
4061 keytrans_add("KP_Left", "Left");
4062 keytrans_add("KP_Up", "Up");
4063 keytrans_add("KP_Right", "Right");
4064 keytrans_add("KP_Down", "Down");
4065 keytrans_add("KP_Prior", "Prior");
4066 keytrans_add("KP_Next", "Next");
4067 keytrans_add("KP_End", "End");
4068 keytrans_add("KP_Begin", "Begin");
4069 keytrans_add("KP_Insert", "Insert");
4070 keytrans_add("KP_Delete", "Delete");
4072 keybind_add("shift+Insert", "PastePrimary");
4073 keybind_add("alt+Insert", "PasteSecondary");
4075 keymap_add("BackSpace", "\177");
4076 keymap_add("Insert", "\x1b[2~");
4077 keymap_add("Delete", "\x1b[3~");
4078 keymap_add("Home", "\x1b[1~");
4079 keymap_add("End", "\x1b[4~");
4080 keymap_add("Prior", "\x1b[5~");
4081 keymap_add("Next", "\x1b[6~");
4082 keymap_add("F1", "\x1bOP");
4083 keymap_add("F2", "\x1bOQ");
4084 keymap_add("F3", "\x1bOR");
4085 keymap_add("F4", "\x1bOS");
4086 keymap_add("F5", "\x1b[15~");
4087 keymap_add("F6", "\x1b[17~");
4088 keymap_add("F7", "\x1b[18~");
4089 keymap_add("F8", "\x1b[19~");
4090 keymap_add("F9", "\x1b[20~");
4091 keymap_add("F10", "\x1b[21~");
4092 keymap_add("Up", "\x1bOA");
4093 keymap_add("Down", "\x1bOB");
4094 keymap_add("Right", "\x1bOC");
4095 keymap_add("Left", "\x1bOD");
4096 keymap_add("kpad+Up", "\x1bOA");
4097 keymap_add("kpad+Down", "\x1bOB");
4098 keymap_add("kpad+Right", "\x1bOC");
4099 keymap_add("kpad+Left", "\x1bOD");
4103 ////////////////////////////////////////////////////////////////////////////////
4104 static Term
*oldTerm
;
4105 static int oldTermIdx
;
4106 static Term
*newTerm
;
4107 static int newTermIdx
;
4108 static int newTermSwitch
;
4111 typedef void (*CmdHandlerFn
) (const char *cmdname
, char *argstr
);
4119 static void cmdPastePrimary (const char *cmdname
, char *argstr
) {
4120 selpaste(XA_PRIMARY
);
4124 static void cmdPasteSecondary (const char *cmdname
, char *argstr
) {
4125 selpaste(XA_SECONDARY
);
4129 static void cmdPasteClipboard (const char *cmdname
, char *argstr
) {
4130 selpaste(XA_CLIPBOARD
);
4134 static void cmdExec (const char *cmdname
, char *argstr
) {
4135 if (term
->execcmd
!= NULL
) free(term
->execcmd
);
4137 term
->execcmd
= strdup(argstr
);
4139 term
->execcmd
= NULL
;
4144 static int parseTabArgs (char *argstr
, int *noswitch
, int nowrap
, int idx
) {
4148 while (*argstr
&& isspace(*argstr
)) ++argstr
;
4149 if (!argstr
[0]) break;
4150 if (iniParseArguments(argstr
, "s-R-", &arg
, &argstr
) != NULL
) break;
4152 if (strcasecmp(arg
, "noswitch") == 0) *noswitch
= 1;
4153 else if (strcasecmp(arg
, "switch") == 0) *noswitch
= 0;
4154 else if (strcasecmp(arg
, "nowrap") == 0) nowrap
= 1;
4155 else if (strcasecmp(arg
, "wrap") == 0) nowrap
= 0;
4156 else if (strcasecmp(arg
, "first") == 0) idx
= 0;
4157 else if (strcasecmp(arg
, "last") == 0) idx
= term_count
-1;
4158 else if (strcasecmp(arg
, "prev") == 0) idx
= -1;
4159 else if (strcasecmp(arg
, "next") == 0) idx
= -2;
4164 n
= strtol(arg
, &eptr
, 0);
4165 if (!eptr
[0] && n
>= 0 && n
< term_count
) idx
= n
;
4170 if ((idx
= termidx
-1) < 0) idx
= nowrap
? 0 : term_count
-1;
4173 if ((idx
= termidx
+1) >= term_count
) idx
= nowrap
? term_count
-1 : 0;
4180 static void flushNewTerm (void) {
4181 if (newTerm
!= NULL
) {
4183 termidx
= newTermIdx
;
4184 tinitialize(term_array
[0]->col
, term_array
[0]->row
);
4186 term
->picbufh
= term
->row
*xw
.ch
;
4187 term
->picbufw
= term
->col
*xw
.cw
;
4188 term
->picbuf
= XCreatePixmap(xw
.dpy
, xw
.win
, term
->picbufw
, term
->picbufh
, XDefaultDepth(xw
.dpy
, xw
.scr
));
4189 XFillRectangle(xw
.dpy
, term
->picbuf
, dc
.gc
, 0, 0, term
->picbufw
, term
->picbufh
);
4191 if (ttynew(term
) != 0) {
4193 termidx
= oldTermIdx
;
4194 termfree(newTermIdx
);
4198 if (newTermSwitch
) {
4201 switchToTerm(newTermIdx
, 1);
4203 oldTermIdx
= termidx
;
4206 termidx
= oldTermIdx
;
4214 static void cmdNewTab (const char *cmdname
, char *argstr
) {
4215 int noswitch
= 0, idx
;
4218 if ((newTerm
= termalloc()) == NULL
) return;
4219 /*idx =*/ parseTabArgs(argstr
, &noswitch
, 0, termidx
);
4221 if (!noswitch
) oldTermIdx
= idx
;
4222 newTermIdx
= termidx
= idx
;
4224 newTermSwitch
= !noswitch
;
4228 static void cmdCloseTab (const char *cmdname
, char *argstr
) {
4230 if (!term
->dead
) kill(term
->pid
, SIGTERM
);
4234 static void cmdKillTab (const char *cmdname
, char *argstr
) {
4236 if (!term
->dead
) kill(term
->pid
, SIGKILL
);
4240 static void cmdSwitchToTab (const char *cmdname
, char *argstr
) {
4244 switchToTerm(parseTabArgs(argstr
, &noswitch
, 0, -2), 1);
4246 oldTermIdx
= termidx
;
4250 static void cmdMoveTabTo (const char *cmdname
, char *argstr
) {
4251 int noswitch
= 0, idx
;
4254 idx
= parseTabArgs(argstr
, &noswitch
, 0, termidx
);
4255 if (idx
!= termidx
&& idx
>= 0 && idx
< term_count
) {
4256 Term
*t
= term_array
[termidx
];
4258 // remove current term
4259 for (int f
= termidx
+1; f
< term_count
; ++f
) term_array
[f
-1] = term_array
[f
];
4261 for (int f
= term_count
-2; f
>= idx
; --f
) term_array
[f
+1] = term_array
[f
];
4262 term_array
[idx
] = t
;
4271 static void cmdDefaultFG (const char *cmdname
, char *argstr
) {
4274 if (iniParseArguments(argstr
, "i{0,511}", &c
) == NULL
) term
->deffg
= c
;
4278 static void cmdDefaultBG (const char *cmdname
, char *argstr
) {
4281 if (iniParseArguments(argstr
, "i{0,511}", &c
) == NULL
) term
->defbg
= c
;
4285 static void scrollHistory (int delta
) {
4286 if (term
->maxhistory
< 1) return; // no history
4287 term
->topline
+= delta
;
4288 if (term
->topline
> term
->maxhistory
-term
->row
) term
->topline
= term
->maxhistory
-term
->row
;
4289 if (term
->topline
< 0) term
->topline
= 0;
4295 static void cmdScrollHistoryLineUp (const char *cmdname
, char *argstr
) {
4300 static void cmdScrollHistoryPageUp (const char *cmdname
, char *argstr
) {
4301 scrollHistory(term
->row
);
4305 static void cmdScrollHistoryLineDown (const char *cmdname
, char *argstr
) {
4310 static void cmdScrollHistoryPageDown (const char *cmdname
, char *argstr
) {
4311 scrollHistory(-term
->row
);
4315 static void cmdScrollHistoryTop (const char *cmdname
, char *argstr
) {
4316 scrollHistory(term
->linecount
);
4320 static void cmdScrollHistoryBottom (const char *cmdname
, char *argstr
) {
4321 scrollHistory(-term
->linecount
);
4325 static const Command commandList
[] = {
4326 {"PastePrimary", cmdPastePrimary
},
4327 {"PasteSecondary", cmdPasteSecondary
},
4328 {"PasteClipboard", cmdPasteClipboard
},
4330 {"NewTab", cmdNewTab
}, // 'noswitch' 'next' 'prev' 'first' 'last'
4331 {"CloseTab", cmdCloseTab
},
4332 {"KillTab", cmdKillTab
},
4333 {"SwitchToTab", cmdSwitchToTab
}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
4334 {"MoveTabTo", cmdMoveTabTo
}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
4335 {"defaultfg", cmdDefaultFG
},
4336 {"defaultbg", cmdDefaultBG
},
4337 {"ScrollHistoryLineUp", cmdScrollHistoryLineUp
},
4338 {"ScrollHistoryPageUp", cmdScrollHistoryPageUp
},
4339 {"ScrollHistoryLineDown", cmdScrollHistoryLineDown
},
4340 {"ScrollHistoryPageDown", cmdScrollHistoryPageDown
},
4341 {"ScrollHistoryTop", cmdScrollHistoryTop
},
4342 {"ScrollHistoryBottom", cmdScrollHistoryBottom
},
4344 {"term", cmdTermName},
4345 {"title", cmdWinTitle},
4346 {"tabsize", cmdTabSize},
4347 {"defaultcursorfg", cmdDefaultCursorFG},
4348 {"defaultcursorbg", cmdDefaultCursorBG},
4349 {"defaultinactivecursorfg", cmdDefaultInactiveCursorFG},
4350 {"defaultinactivecursorbg", cmdDefaultInactiveCursorBG},
4351 {"defaultboldfg", cmdDefaultBoldFG},
4352 {"defaultunderlinefg", cmdDefaultUnderlineFG},
4358 static void executeCommand (const char *str
, int slen
) {
4362 if (str
== NULL
) return;
4363 if (slen
< 0) slen
= strlen(str
);
4365 for (int f
= 0; f
< slen
; ++f
) if (!str
[f
]) { slen
= f
; break; }
4367 while (slen
> 0 && isspace(*str
)) { ++str
; --slen
; }
4368 if (slen
< 1 || !str
[0]) return;
4370 for (e
= str
; slen
> 0 && !isspace(*e
); ++e
, --slen
) ;
4372 if (e
-str
> 127) return;
4373 cmdname
= alloca(e
-str
+8);
4374 if (cmdname
== NULL
) return;
4375 memcpy(cmdname
, str
, e
-str
);
4377 while (slen
> 0 && isspace(*e
)) { ++e
; --slen
; }
4379 for (int f
= 0; commandList
[f
].name
!= NULL
; ++f
) {
4380 if (strcasecmp(commandList
[f
].name
, cmdname
) == 0) {
4381 char *left
= calloc(slen
+2, 1);
4384 if (slen
> 0) memcpy(left
, e
, slen
);
4385 //fprintf(stderr, "command: [%s]; args: [%s]\n", cmdname, left);
4386 commandList
[f
].fn(cmdname
, left
);
4395 static void executeCommands (const char *str
) {
4397 oldTermIdx
= termidx
;
4400 if (str
== NULL
) return;
4405 while (*str
&& isspace(*str
)) ++str
;
4407 if (*str
== ';') { ++str
; continue; }
4412 if (*ce
== ';' && qch
== ' ') break;
4413 if (qch
!= ' ' && *ce
== qch
) { qch
= ' '; ++ce
; continue; }
4414 if (*ce
== '"' || *ce
== '\'') {
4415 if (qch
== ' ') qch
= *ce
;
4419 if (*ce
++ == '\\' && *ce
) ++ce
;
4422 executeCommand(str
, ce
-str
);
4423 if (*ce
) str
= ce
+1; else break;
4426 switchToTerm(oldTermIdx
, 1);
4430 ////////////////////////////////////////////////////////////////////////////////
4431 int main (int argc
, char *argv
[]) {
4432 char *configfile
= NULL
;
4436 for (int f
= 1; f
< argc
; f
++) {
4437 if (strcmp(argv
[f
], "-name") == 0) { ++f
; continue; }
4438 if (strcmp(argv
[f
], "-into") == 0) { ++f
; continue; }
4439 if (strcmp(argv
[f
], "-embed") == 0) { ++f
; continue; }
4440 switch (argv
[f
][0] != '-' || argv
[f
][2] ? -1 : argv
[f
][1]) {
4441 case 'e': f
= argc
+1; break;
4451 opt_term
= strdup(argv
[f
]);
4452 opt_term_locked
= 1;
4457 if (configfile
) free(configfile
);
4458 configfile
= strdup(argv
[f
]);
4464 fprintf(stderr
, "%s", USAGE
);
4469 initDefaultOptions();
4470 if (configfile
== NULL
) {
4471 const char *home
= getenv("HOME");
4474 configfile
= SPrintf("%s/.sterm.rc", home
);
4475 if (loadConfig(configfile
) == 0) goto cfgdone
;
4476 free(configfile
); configfile
= NULL
;
4478 configfile
= SPrintf("%s/.config/sterm.rc", home
);
4479 if (loadConfig(configfile
) == 0) goto cfgdone
;
4480 free(configfile
); configfile
= NULL
;
4483 configfile
= SPrintf("/etc/sterm.rc");
4484 if (loadConfig(configfile
) == 0) goto cfgdone
;
4485 free(configfile
); configfile
= NULL
;
4487 configfile
= SPrintf("/etc/sterm/sterm.rc");
4488 if (loadConfig(configfile
) == 0) goto cfgdone
;
4489 free(configfile
); configfile
= NULL
;
4491 configfile
= SPrintf("./.sterm.rc");
4492 if (loadConfig(configfile
) == 0) goto cfgdone
;
4493 free(configfile
); configfile
= NULL
;
4496 if (loadConfig(configfile
) < 0) die("config file '%s' not found!", configfile
);
4499 if (configfile
!= NULL
) free(configfile
);
4501 for (int f
= 1; f
< argc
; f
++) {
4502 if (strcmp(argv
[f
], "-name") == 0) { ++f
; continue; }
4503 if (strcmp(argv
[f
], "-into") == 0 || strcmp(argv
[f
], "-embed") == 0) {
4504 if (opt_embed
) free(opt_embed
);
4505 opt_embed
= strdup(argv
[f
]);
4508 switch (argv
[f
][0] != '-' || argv
[f
][2] ? -1 : argv
[f
][1]) {
4512 opt_title
= strdup(argv
[f
]);
4518 opt_class
= strdup(argv
[f
]);
4523 if (opt_embed
) free(opt_embed
);
4524 opt_embed
= strdup(argv
[f
]);
4528 /* eat every remaining arguments */
4529 if (++f
< argc
) opt_cmd
= &argv
[f
];
4536 if (configfile
) free(configfile
);
4537 configfile
= strdup(argv
[f
]);
4543 fprintf(stderr
, "%s", USAGE
);
4548 setenv("TERM", opt_term
, 1);
4550 setlocale(LC_ALL
, "");
4555 if (term
->execcmd
!= NULL
) { free(term
->execcmd
); term
->execcmd
= NULL
; }
4556 tinitialize(80, 25);
4557 if (ttynew(term
) != 0) die("can't run process");