1 /* See LICENSE for licence details. */
2 #define VERSION "0.3.1.beta1"
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>
40 // uncomment the following to use XCreateGlyphCursor() instead of XCreatePixmapCursor()
41 //#define BLANKPTR_USE_GLYPH_CURSOR
44 //#define DUMP_KEYSYMS
48 //#define DUMP_PROG_OUTPUT
49 //#define DUMP_PROG_INPUT
51 //#define DUMP_IO_READ
52 //#define DUMP_IO_WRITE
54 #if defined(DUMP_IO_READ) || defined(DUMP_IO_WRITE)
59 # define DUMP_KEYPAD_SWITCH(strflag,strstate) do { fprintf(stderr, "KEYPAD %s (%s)\n", (strstate), (strflag)); } while (0)
61 # define DUMP_KEYPAD_SWITCH(strflag,strstate) ((void)(sizeof(strflag)+sizeof(strstate)))
65 ////////////////////////////////////////////////////////////////////////////////
67 "sterm " VERSION " (c) 2010-2012 st engineers and Ketmar // Vampire Avalon\n" \
68 "usage: sterm [-t title] [-c class] [-w windowid] [-T termname] [-C config_file] [-l langiconv] [-S] [-v] [-e command...]\n"
71 ////////////////////////////////////////////////////////////////////////////////
72 #define FONT "-*-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"
73 #define FONTBOLD "-*-fixed-bold-r-normal-*-18-*-*-*-*-*-*-*"
74 #define FONTTAB "-*-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*"
77 /* Default shell to use if SHELL is not set in the env */
78 #define SHELL "/bin/sh"
81 /* Terminal colors (16 first used in escape sequence) */
82 static const char *defcolornames
[] = {
103 /* 8 normal colors */
112 /* 8 bright colors */
125 /* more colors can be added after 255 to use with DefaultXX */
126 static const char *defextcolornames
[] = {
129 /* root terminal fg and bg */
132 /* bold and underline */
138 /* Default colors (colorname index) foreground, background, cursor, unfocused cursor */
139 #define DEFAULT_FG (7)
140 #define DEFAULT_BG (0)
141 #define DEFAULT_CS (256)
142 #define DEFAULT_UCS (257)
144 #define TNAME "xterm"
146 /* double-click timeout (in milliseconds) between clicks for selection */
147 #define DOUBLECLICK_TIMEOUT (300)
148 #define TRIPLECLICK_TIMEOUT (2*DOUBLECLICK_TIMEOUT)
149 //#define SELECT_TIMEOUT 20 /* 20 ms */
150 #define DRAW_TIMEOUT 18 /* 18 ms */
155 ////////////////////////////////////////////////////////////////////////////////
156 #define MAX_COLOR (511)
158 /* XEMBED messages */
159 #define XEMBED_FOCUS_IN (4)
160 #define XEMBED_FOCUS_OUT (5)
163 /* Arbitrary sizes */
164 #define ESC_TITLE_SIZ (256)
165 #define ESC_BUF_SIZ (256)
166 #define ESC_ARG_SIZ (16)
167 #define DRAW_BUF_SIZ (2048)
169 #define OBUFSIZ (256)
170 #define WBUFSIZ (256)
173 /* masks for key translation */
174 #define XK_NO_MOD (UINT_MAX)
175 #define XK_ANY_MOD (0)
178 /* misc utility macros */
179 //#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000+(t1.tv_usec-t2.tv_usec)/1000)
180 #define SERRNO strerror(errno)
181 #define MIN(a, b) ((a) < (b) ? (a) : (b))
182 #define MAX(a, b) ((a) < (b) ? (b) : (a))
183 #define LEN(a) (sizeof(a)/sizeof(a[0]))
184 #define DEFAULT(a, b) (a) = ((a) ? (a) : (b))
185 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
186 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
187 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
188 #define IS_SET(flag) (term->mode&(flag))
189 #define X2COL(x) ((x)/xw.cw)
190 #define Y2ROW(y) ((y-(opt_tabposition==1 ? xw.tabheight : 0))/xw.ch-(term!=NULL ? term->topline : 0))
191 #define IS_GFX(mode) (((mode)&(ATTR_GFX|ATTR_G1)) == ATTR_GFX || ((mode)&(ATTR_GFX1|ATTR_G1)) == (ATTR_GFX1|ATTR_G1))
194 ////////////////////////////////////////////////////////////////////////////////
201 enum glyph_attribute
{
204 ATTR_UNDERLINE
= 0x02,
213 enum cursor_movement
{
229 GLYPH_SET
= 0x01, /* for selection only */
237 MODE_APPKEYPAD
= 0x04,
238 MODE_ALTSCREEN
= 0x08,
240 MODE_MOUSEBTN
= 0x20,
241 MODE_MOUSEMOTION
= 0x40,
242 MODE_MOUSE
= 0x20|0x40,
244 MODE_BRACPASTE
= 0x100,
245 MODE_FOCUSEVT
= 0x200,
246 MODE_DISPCTRL
= 0x400, //TODO: not implemented yet
254 ESC_ALTCHARSET
= 0x10,
268 enum { B0
=1, B1
=2, B2
=4, B3
=8, B4
=16, B5
=32, B6
=64, B7
=128 };
271 ////////////////////////////////////////////////////////////////////////////////
272 typedef unsigned char uchar
;
273 typedef unsigned int uint
;
274 typedef unsigned long ulong
;
275 typedef unsigned short ushort
;
278 typedef struct __attribute__((packed
)) {
279 char c
[UTF_SIZ
]; /* character code */
280 uchar mode
; /* attribute flags */
281 ushort fg
; /* foreground */
282 ushort bg
; /* background */
283 uchar state
; /* state flags */
290 Glyph attr
; /* current char attributes */
297 /* CSI Escape sequence structs */
298 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
300 char buf
[ESC_BUF_SIZ
]; /* raw string */
301 int len
; /* raw string length */
303 int arg
[ESC_ARG_SIZ
];
304 int narg
; /* nb of args */
309 /* Purely graphic info */
320 int w
; /* window width */
321 int h
; /* window height */
322 int bufw
; /* pixmap width */
323 int bufh
; /* pixmap height */
324 int ch
; /* char height */
325 int cw
; /* char width */
326 char state
; /* focus, redraw, visible */
328 int tch
; /* tab text char height */
331 //struct timeval lastdraw;
335 /* TODO: use better name for vars... */
340 struct { int x
, y
; } b
, e
;
348 /* Drawing Context */
350 ulong
*ncol
; // normal colors
351 ulong
*bcol
; // b/w colors
352 ulong
*gcol
; // green colors
366 #define CMDLINE_SIZE (256)
368 /* Internal representation of the screen */
373 int needConv
; /* 0: utf-8 locale */
377 int row
; /* nb row */
378 int col
; /* nb col */
379 int topline
; /* top line for drawing (0: no history; 1: show one history line; etc) */
380 int linecount
; /* full, with history */
381 int maxhistory
;/* max history lines; 0: none; <0: infinite */
382 Line
*line
; /* screen */
383 Line
*alt
; /* alternate screen */
384 char *dirty
; /* dirtyness of lines */
385 TCursor c
; /* cursor */
386 int top
; /* top scroll limit */
387 int bot
; /* bottom scroll limit */
388 int mode
; /* terminal mode flags */
389 int esc
; /* escape state flags */
391 TCursor csaved
; /* saved cursor info */
392 // old cursor position
396 char title
[ESC_TITLE_SIZ
+1];
404 #ifdef DUMP_PROG_OUTPUT
412 char drawbuf
[DRAW_BUF_SIZ
];
438 char cmdline
[UTF_SIZ
*CMDLINE_SIZE
];
440 char cmdc
[UTF_SIZ
+1];
445 ////////////////////////////////////////////////////////////////////////////////
447 static ushort
*unimap
= NULL
; // 0 or 65535 items; 127: special; high bit set: use alt(gfx) mode; 0: empty
449 static char **opt_cmd
= NULL
;
450 static char *opt_title
= NULL
;
451 static char *opt_embed
= NULL
;
452 static char *opt_class
= NULL
;
453 static char *opt_term
= NULL
;
454 static char *opt_fontnorm
= NULL
;
455 static char *opt_fontbold
= NULL
;
456 static char *opt_fonttab
= NULL
;
457 static char *opt_shell
= NULL
;
458 static char *opt_colornames
[512];
459 static int opt_term_locked
= 0;
460 static int opt_doubleclick_timeout
= DOUBLECLICK_TIMEOUT
;
461 static int opt_tripleclick_timeout
= TRIPLECLICK_TIMEOUT
;
462 static int opt_tabsize
= TAB
;
463 static int defaultFG
= DEFAULT_FG
;
464 static int defaultBG
= DEFAULT_BG
;
465 static int defaultCursorFG
= 0;
466 static int defaultCursorBG
= DEFAULT_CS
;
467 static int defaultCursorInactiveFG
= 0;
468 static int defaultCursorInactiveBG
= DEFAULT_UCS
;
469 static int defaultBoldFG
= -1;
470 static int defaultUnderlineFG
= -1;
471 static int normalTabFG
= 258;
472 static int normalTabBG
= 257;
473 static int activeTabFG
= 258;
474 static int activeTabBG
= 0;
475 static int opt_maxhistory
= 512;
476 static int opt_ptrblank
= 2000; // delay; 0: never
477 static int opt_tabcount
= 6;
478 static int opt_tabposition
= 0;
479 static int opt_drawtimeout
= DRAW_TIMEOUT
;
480 static int opt_disabletabs
= 0;
481 static int opt_audiblebell
= 1;
482 static int ptrBlanked
= 0;
483 static int ptrLastMove
= 0;
484 static int globalBW
= 0;
486 static Term
**term_array
= NULL
;
487 static int term_count
= 0;
488 static int term_array_size
= 0;
489 static Term
*term
; // current terminal
490 static int termidx
; // current terminal index; DON'T RELAY ON IT!
491 static int updateTabBar
;
492 static int lastDrawTime
= 0;
493 static char *lastSelStr
= NULL
;
494 //static int lastSelLength = 0;
496 static int exitcode
= 0;
501 static Atom XA_VT_SELECTION
;
502 static Atom XA_CLIPBOARD
;
504 static Atom XA_TARGETS
;
505 static Atom XA_NETWM_NAME
;
508 ////////////////////////////////////////////////////////////////////////////////
515 static KeyTransDef
*keytrans
= NULL
;
516 static int keytrans_size
= 0;
517 static int keytrans_used
= 0;
528 static KeyInfoDef
*keybinds
= NULL
;
529 static int keybinds_size
= 0;
530 static int keybinds_used
= 0;
532 static KeyInfoDef
*keymap
= NULL
;
533 static int keymap_size
= 0;
534 static int keymap_used
= 0;
537 ////////////////////////////////////////////////////////////////////////////////
538 static void executeCommands (const char *str
);
540 static void ttyresize (void);
541 static void tputc (const char *c
); // `c` is utf-8
542 static void ttywrite (const char *s
, size_t n
);
543 static void tsetdirt (int top
, int bot
);
544 static void tfulldirt (void);
546 static void xseturgency (int add
);
547 static void xfixsel (void);
548 static void xblankPointer (void);
549 static void xunblankPointer (void);
551 static void draw (int forced
);
553 static void tcmdput (const char *s
, int len
);
556 static inline void ttywritestr (const char *s
) { if (s
!= NULL
&& s
[0]) ttywrite(s
, strlen(s
)); }
559 static inline ulong
getColor (int idx
) {
560 if (globalBW
) return dc
.clrs
[globalBW
][idx
];
561 if (term
!= NULL
) return dc
.clrs
[term
->blackandwhite
][idx
];
562 return dc
.clrs
[0][idx
];
564 if (globalBW) return dc.bcol[idx];
566 return (term->blackandwhite ? dc.bcol[idx] : dc.ncol[idx]);
573 ////////////////////////////////////////////////////////////////////////////////
575 static void trimstr (char *s
) {
578 while (*s
&& isspace(*s
)) ++s
;
579 for (e
= s
+strlen(s
); e
> s
; --e
) if (!isspace(e
[-1])) break;
580 if (e
<= s
) *s
= 0; else *e
= 0;
584 // parse the argument list
585 // return error message or NULL
588 // 's': string (char *)
589 // 'i': integer (int *)
590 // 'b': boolean (int *)
591 // '|': optional arguments follows
592 // '.': stop parsing, ignore rest
593 // 'R': stop parsing, set rest ptr (char *)
594 // string modifiers (also for 'R'):
595 // '!' -- don't allow empty strings
596 // '-' -- trim spaces
601 // WARNING! `line` will be modified!
602 // WARNING! string pointers will point INSIDE `line`, so don't discard it!
604 const char *iniParseArguments (char *line
, const char *fmt
, ...) {
608 if (line
== NULL
) return "alas";
612 char spec
= *fmt
++, *args
;
614 if (spec
== '|') { inOptional
= 1; continue; }
615 if (spec
== '.') { va_end(ap
); return NULL
; }
617 while (*line
&& isspace(*line
)) ++line
;
618 if (*line
== '#') *line
= 0;
621 char **p
= va_arg(ap
, char **);
625 if (*fmt
== '!') { ++fmt
; noempty
= 1; }
626 else if (*fmt
== '-') { ++fmt
; trimstr(line
); }
630 if (noempty
&& !line
[0]) return "invalid empty arg";
631 if (p
!= NULL
) *p
= line
;
636 // end of line, stop right here
638 if (!inOptional
) return "out of args";
644 char *dest
= args
, qch
= '#';
647 if (line
[0] == '"' || line
[0] == '\'') qch
= *line
++;
649 while (*line
&& *line
!= qch
) {
650 if (qch
== '#' && isspace(*line
)) break;
653 if (!line
[1]) { va_end(ap
); return "invalid escape"; }
655 case 'n': *dest
++ = '\n'; ++line
; break;
656 case 'r': *dest
++ = '\r'; ++line
; break;
657 case 't': *dest
++ = '\t'; ++line
; break;
658 case 'a': *dest
++ = '\a'; ++line
; break;
659 case 'e': *dest
++ = '\x1b'; ++line
; break; // esc
660 case 's': *dest
++ = ' '; ++line
; break;
663 if (!isxdigit(*line
)) { va_end(ap
); return "invalid hex escape"; }
664 n
= toupper(*line
)-'0'; if (n
> 9) n
-= 7;
666 if (isxdigit(*line
)) {
667 int b
= toupper(*line
)-'0'; if (b
> 9) b
-= 7;
676 for (int f
= 0; f
< 4; ++f
) {
677 if (*line
< '0' || *line
> '7') break;
678 n
= (n
*8)+(line
[0]-'0');
679 if (n
> 255) { va_end(ap
); return "invalid oct escape"; }
682 if (n
== 0) { va_end(ap
); return "invalid oct escape"; }
685 case '1'...'9': // decimal
687 for (int f
= 0; f
< 3; ++f
) {
688 if (*line
< '0' || *line
> '9') break;
689 n
= (n
*8)+(line
[0]-'0');
690 if (n
> 255) { va_end(ap
); return "invalid dec escape"; }
693 if (n
== 0) { va_end(ap
); return "invalid oct escape"; }
705 if (*line
!= qch
) return "unfinished string";
707 } else if (*line
&& *line
!= '#') ++line
;
710 // now process and convert argument
714 case 's': { /* string */
715 int noempty
= 0, trim
= 0;
719 if (*fmt
== '!') { noempty
= 1; ++fmt
; }
720 else if (*fmt
== '-') { trim
= 1; ++fmt
; }
724 if (trim
) trimstr(args
);
726 if (noempty
&& !args
[0]) { va_end(ap
); return "invalid empty string"; }
727 p
= va_arg(ap
, char **);
728 if (p
!= NULL
) *p
= args
;
733 return "invalid integer";
735 int *p
= va_arg(ap
, int *);
740 n
= strtol(args
, &eptr
, 0);
741 if (*eptr
) { va_end(ap
); return "invalid integer"; }
745 int minmax
[2], haveminmax
[2];
747 haveminmax
[0] = haveminmax
[1] = 0;
748 minmax
[0] = minmax
[1] = 0;
750 for (int f
= 0; f
< 2; ++f
) {
751 if (isdigit(*fmt
) || *fmt
== '-' || *fmt
== '+') {
754 if (*fmt
== '-') neg
= 1;
755 if (!isdigit(*fmt
)) {
757 if (!isdigit(*fmt
)) { va_end(ap
); return "invalid integer bounds"; }
759 while (isdigit(*fmt
)) {
760 minmax
[f
] = minmax
[f
]*10+(fmt
[0]-'0');
763 if (neg
) minmax
[f
] = -minmax
[f
];
764 //fprintf(stderr, "got: %d\n", minmax[f]);
767 if (f
== 1) { va_end(ap
); return "invalid integer bounds: extra comma"; }
768 // do nothing, we are happy
770 } else if (*fmt
== '}') {
773 } else { va_end(ap
); return "invalid integer bounds"; }
775 if (*fmt
!= '}') { va_end(ap
); return "invalid integer bounds"; }
778 //fprintf(stderr, "b: (%d,%d) (%d,%d)\n", haveminmax[0], minmax[0], haveminmax[1], minmax[1]);
779 if ((haveminmax
[0] && n
< minmax
[0]) || (haveminmax
[1] && n
> minmax
[1])) { va_end(ap
); return "integer out of bounds"; }
785 case 'b': { /* bool */
786 int *p
= va_arg(ap
, int *);
789 if (!args
[0]) { va_end(ap
); return "invalid boolean"; }
790 if (strcasecmp(args
, "true") == 0 || strcasecmp(args
, "on") == 0 ||
791 strcasecmp(args
, "tan") == 0 || strcasecmp(args
, "1") == 0) {
793 } else if (strcasecmp(args
, "false") == 0 || strcasecmp(args
, "off") == 0 ||
794 strcasecmp(args
, "ona") == 0 || strcasecmp(args
, "0") == 0) {
799 return "invalid boolean";
804 return "invalid format specifier";
808 while (*line
&& isspace(*line
)) ++line
;
809 if (!line
[0] || line
[0] == '#') return NULL
;
814 ////////////////////////////////////////////////////////////////////////////////
816 static int utf8decode (const char *s
, ulong
*u
) {
822 if (~c
& B7
) { /* 0xxxxxxx */
825 } else if ((c
& (B7
|B6
|B5
)) == (B7
|B6
)) { /* 110xxxxx */
826 *u
= c
&(B4
|B3
|B2
|B1
|B0
);
828 } else if ((c
& (B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
)) { /* 1110xxxx */
829 *u
= c
&(B3
|B2
|B1
|B0
);
831 } else if ((c
& (B7
|B6
|B5
|B4
|B3
)) == (B7
|B6
|B5
|B4
)) { /* 11110xxx */
838 for (int f
= n
; f
> 0; --f
, ++rtn
, ++s
) {
840 if ((c
& (B7
|B6
)) != B7
) goto invalid
; /* 10xxxxxx */
842 *u
|= c
& (B5
|B4
|B3
|B2
|B1
|B0
);
844 if ((n
== 1 && *u
< 0x80) ||
845 (n
== 2 && *u
< 0x800) ||
846 (n
== 3 && *u
< 0x10000) ||
847 (*u
>= 0xD800 && *u
<= 0xDFFF)) {
857 static int utf8encode (ulong u
, char *s
) {
865 *sp
= uc
; /* 0xxxxxxx */
867 } else if (uc
< 0x800) {
868 *sp
= (uc
>> 6) | (B7
|B6
); /* 110xxxxx */
870 } else if (uc
< 0x10000) {
871 *sp
= (uc
>> 12) | (B7
|B6
|B5
); /* 1110xxxx */
873 } else if (uc
<= 0x10FFFF) {
874 *sp
= (uc
>> 18) | (B7
|B6
|B5
|B4
); /* 11110xxx */
880 for (int f
= n
; f
> 0; --f
, ++sp
) *sp
= ((uc
>> 6*(f
-1)) & (B5
|B4
|B3
|B2
|B1
|B0
)) | B7
; /* 10xxxxxx */
891 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
892 UTF-8 otherwise return 0 */
893 static int isfullutf8 (const char *s
, int b
) {
900 if ((*c1
&(B7
|B6
|B5
)) == (B7
|B6
) && b
== 1) return 0;
901 if ((*c1
&(B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
) && (b
== 1 || (b
== 2 && (*c2
&(B7
|B6
)) == B7
))) return 0;
902 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;
907 static int utf8size (const char *s
) {
911 if ((c
&(B7
|B6
|B5
)) == (B7
|B6
)) return 2;
912 if ((c
&(B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
)) return 3;
917 static int utf8strlen (const char *s
) {
921 if (((unsigned char)(s
[0])&0xc0) == 0xc0 || ((unsigned char)(s
[0])&0x80) == 0) ++len
;
928 static void utf8choplast (char *s
) {
931 for (char *t
= s
; *t
; ++t
) {
932 if (((unsigned char)(t
[0])&0xc0) == 0xc0 || ((unsigned char)(t
[0])&0x80) == 0) lastpos
= (int)(t
-s
);
938 ////////////////////////////////////////////////////////////////////////////////
940 static char *SPrintfVA (const char *fmt
, va_list vaorig
) {
945 if (buf
== NULL
) { fprintf(stderr
, "\nFATAL: out of memory!\n"); abort(); }
951 olen
= vsnprintf(buf
, len
, fmt
, va
);
953 if (olen
>= 0 && olen
< len
) return buf
;
954 if (olen
< 0) olen
= len
*2-1;
955 nb
= realloc(buf
, olen
+1);
956 if (nb
== NULL
) { fprintf(stderr
, "\nFATAL: out of memory!\n"); abort(); }
963 static __attribute__((format(printf
,1,2))) char *SPrintf (const char *fmt
, ...) {
968 buf
= SPrintfVA(fmt
, va
);
974 static __attribute__((noreturn
)) __attribute__((format(printf
,1,2))) void die (const char *errstr
, ...) {
977 fprintf(stderr
, "FATAL: ");
978 va_start(ap
, errstr
);
979 vfprintf(stderr
, errstr
, ap
);
981 fprintf(stderr
, "\n");
986 ////////////////////////////////////////////////////////////////////////////////
988 static struct timespec mclk_sttime
; // starting time of monotonic clock
991 static void mclock_init (void) {
992 clock_gettime(CLOCK_MONOTONIC
/*CLOCK_MONOTONIC_RAW*/, &mclk_sttime
);
996 static uint
mclock_ticks (void) {
999 clock_gettime(CLOCK_MONOTONIC
/*CLOCK_MONOTONIC_RAW*/, &tp
);
1000 tp
.tv_sec
-= mclk_sttime
.tv_sec
;
1001 return tp
.tv_sec
*1000+(tp
.tv_nsec
/1000000);
1005 ////////////////////////////////////////////////////////////////////////////////
1006 // locale conversions
1007 static iconv_t icFromLoc
;
1008 static iconv_t icToLoc
;
1009 static int needConversion
= 0;
1010 static const char *cliLocale
= NULL
;
1013 static void initLCConversion (void) {
1014 const char *lct
= setlocale(LC_CTYPE
, NULL
);
1018 if (cliLocale
== NULL
) {
1019 if (strrchr(lct
, '.') != NULL
) lct
= strrchr(lct
, '.')+1;
1023 if (strcasecmp(lct
, "utf8") == 0 || strcasecmp(lct
, "utf-8") == 0) return;
1024 //fprintf(stderr, "locale: [%s]\n", lct);
1025 icFromLoc
= iconv_open("UTF-8", lct
);
1026 if (icFromLoc
== (iconv_t
)-1) die("can't initialize locale conversion");
1027 cstr
= SPrintf("%s//TRANSLIT", lct
);
1028 icToLoc
= iconv_open(cstr
, "UTF-8");
1030 if (icToLoc
== (iconv_t
)-1) die("can't initialize locale conversion");
1035 static int loc2utf (char *dest
, const char *src
, int len
) {
1036 if (needConversion
) {
1044 il
= iconv(icFromLoc
, &ibuf
, &il
, &obuf
, &ol
);
1045 if (il
== (size_t)-1) return 0;
1048 if (len
> 0) memmove(dest
, src
, len
);
1054 static int utf2loc (char *dest
, const char *src
, int len
) {
1055 if (needConversion
) {
1063 il
= iconv(icToLoc
, &ibuf
, &il
, &obuf
, &ol
);
1064 if (il
== (size_t)-1) return 0;
1067 if (len
> 0) memmove(dest
, src
, len
);
1073 ////////////////////////////////////////////////////////////////////////////////
1074 static void fixWindowTitle (const Term
*t
) {
1075 const char *title
= (t
!= NULL
) ? t
->title
: NULL
;
1077 if (title
== NULL
|| !title
[0]) {
1079 if (title
== NULL
) title
= "";
1081 XStoreName(xw
.dpy
, xw
.win
, title
);
1082 XChangeProperty(xw
.dpy
, xw
.win
, XA_NETWM_NAME
, XA_UTF8
, 8, PropModeReplace
, (const unsigned char *)title
, strlen(title
));
1086 // find latest active terminal (but not current %-)
1087 static int findTermToSwitch (void) {
1088 int maxlat
= -1, idx
= -1;
1090 for (int f
= 0; f
< term_count
; ++f
) {
1091 if (term
!= term_array
[f
] && term_array
[f
]->lastActiveTime
> maxlat
) {
1092 maxlat
= term_array
[f
]->lastActiveTime
;
1097 if (termidx
== 0) idx
= 0; else idx
= termidx
+1;
1098 if (idx
> term_count
) idx
= term_count
-1;
1104 static void switchToTerm (int idx
, int redraw
) {
1105 if (idx
>= 0 && idx
< term_count
&& term_array
[idx
] != NULL
&& term_array
[idx
] != term
) {
1106 if (term
!= NULL
) term
->lastActiveTime
= mclock_ticks();
1108 term
= term_array
[termidx
];
1111 fixWindowTitle(term
);
1113 if (redraw
) draw(1);
1114 //FIXME: optimize memory allocations
1115 if (term
->sel
.clip
!= NULL
&& term
->sel
.bx
>= 0) {
1116 if (lastSelStr
!= NULL
) free(lastSelStr
);
1117 lastSelStr
= strdup(term
->sel
.clip
);
1118 //fprintf(stderr, "newsel: [%s]\n", lastSelStr);
1121 //fprintf(stderr, "term #%d\n", termidx);
1122 //fprintf(stderr, "needConv: %d\n", term->needConv);
1127 static Term
*termalloc (void) {
1130 if (term_count
>= term_array_size
) {
1131 int newsz
= (term_count
==0) ? 1 : term_array_size
+64;
1132 Term
**n
= realloc(term_array
, sizeof(Term
*)*newsz
);
1134 if (n
== NULL
&& term_count
== 0) die("out of memory!");
1136 term_array_size
= newsz
;
1138 if ((t
= calloc(1, sizeof(Term
))) == NULL
) return NULL
;
1139 t
->wrbufsize
= WBUFSIZ
;
1140 t
->deffg
= defaultFG
;
1141 t
->defbg
= defaultBG
;
1143 t
->needConv
= (needConversion
? 1 : 0);
1144 t
->audiblebell
= opt_audiblebell
;
1145 term_array
[term_count
++] = t
;
1150 // newer delete last terminal!
1151 static void termfree (int idx
) {
1152 if (idx
>= 0 && idx
< term_count
&& term_array
[idx
] != NULL
) {
1153 Term
*t
= term_array
[idx
];
1156 kill(t
->pid
, SIGKILL
);
1159 if (t
->cmdfd
>= 0) {
1163 exitcode
= t
->exitcode
;
1164 if (idx
== termidx
) {
1165 if (term_count
> 1) {
1167 //switchToTerm((idx > 0) ? idx-1 : 1, 0);
1168 switchToTerm(findTermToSwitch(), 0);
1173 for (int y
= 0; y
< t
->row
; ++y
) free(t
->alt
[y
]);
1174 for (int y
= 0; y
< t
->linecount
; ++y
) {
1175 //fprintf(stderr, "y=%d\n", y);
1181 if (t
->execcmd
!= NULL
) free(t
->execcmd
);
1183 if (termidx
> idx
) {
1184 // not current, and current at the right
1187 for (int f
= idx
+1; f
< term_count
; ++f
) term_array
[f
-1] = term_array
[f
];
1189 XFreePixmap(xw
.dpy
, t
->picbuf
);
1195 static void termcleanup (void) {
1196 int f
= 0, needredraw
= 0;
1198 while (f
< term_count
) {
1199 if (term_array
[f
]->dead
) {
1213 //FIXME: is it safe to assume that signal interrupted main program?
1214 static void sigchld (int a
) {
1215 //if (waitpid(term->pid, &stat, 0) < 0) die("waiting for pid %hd failed: %s", term->pid, SERRNO);
1218 pid_t res
= waitpid(-1, &stat
, WNOHANG
);
1220 if (res
== (pid_t
)-1 || res
== 0) break;
1222 for (int f
= 0; f
< term_count
; ++f
) {
1223 if (term_array
[f
]->pid
== res
) {
1224 // this terminal should die
1225 //if (term_count == 1) exit(0);
1226 //close(term_array[f]->cmdfd);
1227 //term_array[f]->cmdfd = -1;
1228 term_array
[f
]->dead
= 1;
1229 term_array
[f
]->pid
= 0;
1230 term_array
[f
]->exitcode
= (WIFEXITED(stat
)) ? WEXITSTATUS(stat
) : 127;
1231 //fprintf(stderr, "exitcode=%d\n", term_array[f]->exitcode);
1237 signal(SIGCHLD
, sigchld
);
1241 ////////////////////////////////////////////////////////////////////////////////
1242 static void keytrans_reset (void) {
1243 if (keytrans
) free(keytrans
);
1250 static void keytrans_add (const char *src
, const char *dst
) {
1251 KeySym kssrc
= XStringToKeysym(src
);
1252 KeySym ksdst
= XStringToKeysym(dst
);
1254 if (kssrc
== NoSymbol
) die("invalid keysym: '%s'", src
);
1255 if (ksdst
== NoSymbol
) die("invalid keysym: '%s'", dst
);
1256 if (kssrc
== ksdst
) return; // idiot
1258 for (int f
= 0; f
< keytrans_used
; ++f
) {
1259 if (keytrans
[f
].src
== kssrc
) {
1261 keytrans
[f
].dst
= ksdst
;
1266 if (keytrans_used
>= keytrans_size
) {
1267 int newsize
= keytrans_size
+64;
1268 KeyTransDef
*n
= realloc(keytrans
, sizeof(KeyTransDef
)*newsize
);
1270 if (n
== NULL
) die("out of memory");
1271 keytrans_size
= newsize
;
1274 keytrans
[keytrans_used
].src
= kssrc
;
1275 keytrans
[keytrans_used
].dst
= ksdst
;
1280 ////////////////////////////////////////////////////////////////////////////////
1281 static void parsekeyname (const char *str
, KeySym
*ks
, uint
*mask
, int *kp
) {
1282 char *s
= alloca(strlen(str
)+1);
1284 if (s
== NULL
) die("out of memory");
1295 while (*s
&& isspace(*s
)) ++s
;
1296 for (e
= s
; *e
&& !isspace(*e
) && *e
!= '+'; ++e
) ;
1299 if (strcasecmp(s
, "alt") == 0) mm
= Mod1Mask
;
1300 else if (strcasecmp(s
, "win") == 0) mm
= Mod4Mask
;
1301 else if (strcasecmp(s
, "ctrl") == 0) mm
= ControlMask
;
1302 else if (strcasecmp(s
, "shift") == 0) mm
= ShiftMask
;
1303 else if (strcasecmp(s
, "any") == 0) mm
= XK_NO_MOD
; //!
1304 else if (strcasecmp(s
, "kpad") == 0) *kp
= 1;
1307 if ((*ks
= XStringToKeysym(s
)) == NoSymbol
) break;
1308 //fprintf(stderr, "[%s]\n", s);
1313 while (*s
&& isspace(*s
)) ++s
;
1315 if (*s
!= '+') { *ks
= NoSymbol
; break; }
1318 if (mm
== XK_NO_MOD
) *mask
= XK_ANY_MOD
;
1319 else if (*mask
== XK_NO_MOD
) *mask
= mm
;
1320 else if (*mask
!= XK_ANY_MOD
) *mask
|= mm
;
1323 if (*s
) { *ks
= NoSymbol
; break; }
1326 if (*ks
== NoSymbol
) die("invalid key name: '%s'", str
);
1327 //fprintf(stderr, "mask=0x%08x, kp=%d\n", *mask, *kp);
1331 ////////////////////////////////////////////////////////////////////////////////
1332 static void keybinds_reset (void) {
1333 if (keybinds
) free(keybinds
);
1340 static void keybind_add (const char *key
, const char *act
) {
1345 parsekeyname(key
, &ks
, &mask
, &kp
);
1347 for (int f
= 0; f
< keybinds_used
; ++f
) {
1348 if (keybinds
[f
].key
== ks
&& keybinds
[f
].mask
== mask
) {
1349 // replace or remove
1350 free(keybinds
[f
].str
);
1351 if (act
== NULL
|| !act
[0]) {
1353 for (int c
= f
+1; c
< keybinds_used
; ++c
) keybinds
[c
-1] = keybinds
[c
];
1356 if ((keybinds
[f
].str
= strdup(act
)) == NULL
) die("out of memory");
1362 if (keybinds_used
>= keybinds_size
) {
1363 int newsize
= keybinds_size
+64;
1364 KeyInfoDef
*n
= realloc(keybinds
, sizeof(KeyInfoDef
)*newsize
);
1366 if (n
== NULL
) die("out of memory");
1367 keybinds_size
= newsize
;
1370 keybinds
[keybinds_used
].key
= ks
;
1371 keybinds
[keybinds_used
].mask
= mask
;
1372 keybinds
[keybinds_used
].kp
= 0;
1373 if ((keybinds
[keybinds_used
].str
= strdup(act
)) == NULL
) die("out of memory");
1378 ////////////////////////////////////////////////////////////////////////////////
1379 static void keymap_reset (void) {
1380 if (keymap
) free(keymap
);
1387 static void keymap_add (const char *key
, const char *act
) {
1392 parsekeyname(key
, &ks
, &mask
, &kp
);
1394 for (int f
= 0; f
< keymap_used
; ++f
) {
1395 if (keymap
[f
].key
== ks
&& keymap
[f
].mask
== mask
&& keymap
[f
].kp
== kp
) {
1396 // replace or remove
1397 free(keymap
[f
].str
);
1400 for (int c
= f
+1; c
< keymap_used
; ++c
) keymap
[c
-1] = keymap
[c
];
1403 if ((keymap
[f
].str
= strdup(act
)) == NULL
) die("out of memory");
1409 if (keymap_used
>= keymap_size
) {
1410 int newsize
= keymap_size
+128;
1411 KeyInfoDef
*n
= realloc(keymap
, sizeof(KeyInfoDef
)*newsize
);
1413 if (n
== NULL
) die("out of memory");
1414 keymap_size
= newsize
;
1417 keymap
[keymap_used
].key
= ks
;
1418 keymap
[keymap_used
].mask
= mask
;
1419 keymap
[keymap_used
].kp
= kp
;
1420 if ((keymap
[keymap_used
].str
= strdup(act
)) == NULL
) die("out of memory");
1425 ////////////////////////////////////////////////////////////////////////////////
1427 static void inline markDirty (int lineno
, int flag
) {
1428 if (term
!= NULL
&& lineno
>= 0 && lineno
< term
->row
) {
1429 term
->dirty
[lineno
] |= flag
;
1430 term
->wantRedraw
= 1;
1431 term
->lastDrawTime
= mclock_ticks(); //FIXME: avoid excess redraw?
1436 static void selinit (void) {
1437 term
->sel
.tclick1
= term
->sel
.tclick2
= mclock_ticks();
1440 term
->sel
.clip
= NULL
;
1441 term
->sel
.xtarget
= XA_UTF8
;
1442 //if (term->sel.xtarget == None) term->sel.xtarget = XA_STRING;
1446 static void selhide (void) {
1447 if (term
->sel
.bx
!= -1) {
1455 static inline int selected (int x
, int y
) {
1456 if (term
->sel
.bx
== -1) return 0;
1457 if (term
->sel
.ey
== y
&& term
->sel
.by
== y
) {
1458 int bx
= MIN(term
->sel
.bx
, term
->sel
.ex
);
1459 int ex
= MAX(term
->sel
.bx
, term
->sel
.ex
);
1461 return BETWEEN(x
, bx
, ex
);
1464 ((term
->sel
.b
.y
< y
&& y
< term
->sel
.e
.y
) || (y
== term
->sel
.e
.y
&& x
<= term
->sel
.e
.x
)) ||
1465 (y
== term
->sel
.b
.y
&& x
>= term
->sel
.b
.x
&& (x
<= term
->sel
.e
.x
|| term
->sel
.b
.y
!= term
->sel
.e
.y
));
1469 static void getbuttoninfo (XEvent
*e
, int *b
, int *x
, int *y
) {
1470 if (b
!= NULL
) *b
= e
->xbutton
.button
;
1471 if (x
!= NULL
) *x
= X2COL(e
->xbutton
.x
);
1472 if (y
!= NULL
) *y
= Y2ROW(e
->xbutton
.y
);
1473 term
->sel
.b
.x
= (term
->sel
.by
< term
->sel
.ey
? term
->sel
.bx
: term
->sel
.ex
);
1474 term
->sel
.b
.y
= MIN(term
->sel
.by
, term
->sel
.ey
);
1475 term
->sel
.e
.x
= (term
->sel
.by
< term
->sel
.ey
? term
->sel
.ex
: term
->sel
.bx
);
1476 term
->sel
.e
.y
= MAX(term
->sel
.by
, term
->sel
.ey
);
1480 static void mousereport (XEvent
*e
) {
1481 int x
= X2COL(e
->xbutton
.x
);
1482 int y
= Y2ROW(e
->xbutton
.y
);
1483 int button
= e
->xbutton
.button
;
1484 int state
= e
->xbutton
.state
;
1485 //char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
1488 if (term
== NULL
) return;
1489 if (x
< 0 || x
>= term
->col
|| y
< 0 || y
>= term
->row
) return;
1490 sprintf(buf
, "\x1b[M%c%c%c", 0, 32+x
+1, 32+y
+1);
1492 if (e
->xbutton
.type
== MotionNotify
) {
1493 if (!IS_SET(MODE_MOUSEMOTION
) || (x
== term
->mouseox
&& y
== term
->mouseoy
)) return;
1494 button
= term
->mouseob
+32;
1497 } else if (e
->xbutton
.type
== ButtonRelease
|| button
== AnyButton
) {
1501 if (button
>= 3) button
+= 64-3;
1502 if (e
->xbutton
.type
== ButtonPress
) {
1503 term
->mouseob
= button
;
1508 buf
[3] = 32+button
+(state
& ShiftMask
? 4 : 0)+(state
& Mod4Mask
? 8 : 0)+(state
& ControlMask
? 16 : 0);
1513 static void xfixsel (void) {
1514 if (lastSelStr
!= NULL
) {
1515 XSetSelectionOwner(xw
.dpy
, XA_PRIMARY
, xw
.win
, CurrentTime
);
1516 XSetSelectionOwner(xw
.dpy
, XA_CLIPBOARD
, xw
.win
, CurrentTime
);
1518 if (XGetSelectionOwner(xw
.dpy
, XA_PRIMARY
) == xw
.win
) XSetSelectionOwner(xw
.dpy
, XA_PRIMARY
, None
, CurrentTime
);
1519 if (XGetSelectionOwner(xw
.dpy
, XA_CLIPBOARD
) == xw
.win
) XSetSelectionOwner(xw
.dpy
, XA_CLIPBOARD
, None
, CurrentTime
);
1525 static void xsetsel (char *str
) {
1526 /* register the selection for both the clipboard and the primary */
1527 if (term
== NULL
) return;
1528 if (term
->sel
.clip
!= NULL
) free(term
->sel
.clip
);
1529 term
->sel
.clip
= str
;
1530 if (lastSelStr
!= NULL
) free(lastSelStr
);
1531 lastSelStr
= (str
!= NULL
? strdup(str
) : NULL
);
1533 //fprintf(stderr, "[%s]\n", str);
1537 static Line
selgetlinebyy (int y
) {
1540 if (y
>= term
->row
) return NULL
;
1542 if (y
<= -(term
->maxhistory
)) return NULL
;
1543 l
= term
->line
[term
->row
+(-y
)-1];
1551 static void selcopy (void) {
1553 int x
, y
, bufsize
, is_selected
= 0;
1555 if (term
== NULL
|| term
->sel
.bx
== -1) {
1558 int sy
= MIN(term
->sel
.e
.y
, term
->sel
.b
.y
);
1559 int ey
= MAX(term
->sel
.e
.y
, term
->sel
.b
.y
);
1562 fprintf(stderr, "bx=%d; ex=%d; by=%d; ey=%d\n", term->sel.bx, term->sel.by, term->sel.ex, term->sel.ey);
1563 fprintf(stderr, " b.x=%d; e.x=%d; b.y=%d; e.y=%d\n", term->sel.b.x, term->sel.b.y, term->sel.e.x, term->sel.e.y);
1564 fprintf(stderr, " sy=%d; ey=%d\n", sy, ey);
1566 bufsize
= (term
->col
+1)*(ey
-sy
+1)*UTF_SIZ
;
1567 ptr
= str
= malloc(bufsize
);
1568 /* append every set & selected glyph to the selection */
1569 for (y
= sy
; y
<= ey
; ++y
) {
1570 Line l
= selgetlinebyy(y
);
1572 if (y
>= term
->row
) break;
1573 if (l
== NULL
) continue;
1574 for (x
= 0; x
< term
->col
; ++x
) {
1575 is_selected
= selected(x
, y
);
1576 //if (is_selected) term->line[y][x].state |= GLYPH_DIRTY;
1577 if (/*(l[x].state & GLYPH_SET) &&*/ is_selected
) {
1578 int size
= utf8size(l
[x
].c
);
1580 if (size
== 1 && l
[x
].c
[0] == 0) {
1583 memcpy(ptr
, l
[x
].c
, size
);
1588 /* \n at the end of every selected line except for the last one */
1589 if (is_selected
&& y
< ey
) *ptr
++ = '\n';
1597 static void selnotify (XEvent
*e
) {
1598 ulong nitems
, ofs
, rem
;
1602 XSelectionEvent
*se
= (XSelectionEvent
*)e
;
1606 if (term
== NULL
|| term
->cmdMode
== CMDMODE_MESSAGE
) return;
1607 if (se
->property
!= XA_VT_SELECTION
) return;
1608 if (se
->target
== XA_UTF8
) {
1610 } else if (se
->target
== XA_STRING
) {
1615 if (!isutf8
) return;
1618 if (XGetWindowProperty(xw
.dpy
, xw
.win
, se
->property
, ofs
, BUFSIZ
/4, False
, AnyPropertyType
, &type
, &format
, &nitems
, &rem
, &data
)) {
1619 fprintf(stderr
, "Clipboard allocation failed\n");
1622 //fprintf(stderr, "nitems=%d; format=%d; rem=%d\n", (int)nitems, format, (int)rem);
1623 if (term
->cmdMode
!= CMDMODE_NONE
) {
1624 tcmdput((const char *)data
, nitems
*format
/8);
1626 if (nitems
*format
/8 > 0 && !wasbrk
&& IS_SET(MODE_BRACPASTE
)) {
1628 ttywritestr("\x1b[200~");
1630 ttywrite((const char *)data
, nitems
*format
/8);
1633 /* number of 32-bit chunks returned */
1634 ofs
+= nitems
*format
/32;
1637 if (wasbrk
) ttywritestr("\x1b[201~");
1641 static void selpaste (Atom which
) {
1642 if (term
== NULL
) return;
1643 if (XGetSelectionOwner(xw
.dpy
, which
) != None
) {
1644 XConvertSelection(xw
.dpy
, which
, term
->sel
.xtarget
, XA_VT_SELECTION
, xw
.win
, CurrentTime
);
1647 if (which
== XA_PRIMARY
) selpaste(XA_SECONDARY
);
1648 else if (which
== XA_SECONDARY
) selpaste(XA_CLIPBOARD
);
1652 static void selrequest (XEvent
*e
) {
1653 XSelectionRequestEvent
*xsre
;
1654 XSelectionEvent xev
;
1656 if (lastSelStr
== NULL
) return;
1657 xsre
= (XSelectionRequestEvent
*)e
;
1658 xev
.type
= SelectionNotify
;
1659 xev
.requestor
= xsre
->requestor
;
1660 xev
.selection
= xsre
->selection
;
1661 xev
.target
= xsre
->target
;
1662 xev
.time
= xsre
->time
;
1664 xev
.property
= None
;
1665 if (xsre
->target
== XA_TARGETS
) {
1666 /* respond with the supported type */
1667 Atom string
= XA_UTF8
;
1669 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, XA_ATOM
, 32, PropModeReplace
, (uchar
*)&string
, 1);
1670 xev
.property
= xsre
->property
;
1671 } else if (xsre
->target
== XA_UTF8
&& lastSelStr
!= NULL
) {
1672 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, XA_UTF8
, 8, PropModeReplace
, (uchar
*)lastSelStr
, strlen(lastSelStr
));
1673 xev
.property
= xsre
->property
;
1674 } else if (xsre
->target
== XA_STRING
&& lastSelStr
!= NULL
) {
1675 char *s
= malloc(strlen(lastSelStr
)*4+8);
1678 int len
= utf2loc(s
, lastSelStr
, strlen(lastSelStr
));
1680 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, xsre
->target
, 8, PropModeReplace
, (uchar
*)s
, len
);
1681 xev
.property
= xsre
->property
;
1685 /* all done, send a notification to the listener */
1686 if (!XSendEvent(xsre
->display
, xsre
->requestor
, True
, 0, (XEvent
*)&xev
)) fprintf(stderr
, "Error sending SelectionNotify event\n");
1690 static void selclear (XEvent
*e
) {
1691 if (lastSelStr
!= NULL
) free(lastSelStr
);
1694 if (term
->sel
.clip
!= NULL
) free(term
->sel
.clip
);
1695 term
->sel
.clip
= NULL
;
1697 if (term
->sel
.bx
!= 0) {
1706 static void bpress (XEvent
*e
) {
1707 if (term
== NULL
) return;
1709 switch (opt_tabposition
) {
1711 if (e
->xbutton
.y
>= xw
.h
-xw
.tabheight
) return;
1714 if (e
->xbutton
.y
< xw
.tabheight
) return;
1718 if ((e
->xbutton
.state
&ShiftMask
) != 0) {
1719 if (e
->xbutton
.button
== Button1
) {
1720 if (term
->sel
.bx
!= -1) tsetdirt(term
->sel
.b
.y
, term
->sel
.e
.y
);
1722 term
->sel
.ex
= term
->sel
.bx
= X2COL(e
->xbutton
.x
);
1723 term
->sel
.ey
= term
->sel
.by
= Y2ROW(e
->xbutton
.y
);
1724 //fprintf(stderr, "x=%d; y=%d\n", term->sel.bx, term->sel.by);
1729 if (e->xbutton.button == Button3) {
1737 if (IS_SET(MODE_MOUSE
)) mousereport(e
);
1741 static void brelease (XEvent
*e
) {
1742 if (term
== NULL
) return;
1744 switch (opt_tabposition
) {
1746 if (e
->xbutton
.y
>= xw
.h
-xw
.tabheight
) return;
1749 if (e
->xbutton
.y
< xw
.tabheight
) return;
1753 if ((e
->xbutton
.state
&ShiftMask
) == 0 && !term
->sel
.mode
) {
1754 if (IS_SET(MODE_MOUSE
)) mousereport(e
);
1758 if (e
->xbutton
.button
== Button2
) {
1759 selpaste(XA_PRIMARY
);
1760 } else if (e
->xbutton
.button
== Button1
) {
1762 getbuttoninfo(e
, NULL
, &term
->sel
.ex
, &term
->sel
.ey
); // this sets sel.b and sel.e
1764 if (term
->sel
.bx
== term
->sel
.ex
&& term
->sel
.by
== term
->sel
.ey
) {
1765 // single line, single char selection
1768 markDirty(term
->sel
.ey
, 2);
1770 now
= mclock_ticks();
1771 if (now
-term
->sel
.tclick2
<= opt_tripleclick_timeout
) {
1772 /* triple click on the line */
1773 term
->sel
.b
.x
= term
->sel
.bx
= 0;
1774 term
->sel
.e
.x
= term
->sel
.ex
= term
->col
;
1775 term
->sel
.b
.y
= term
->sel
.e
.y
= term
->sel
.ey
;
1776 } else if (now
-term
->sel
.tclick1
<= opt_doubleclick_timeout
) {
1777 /* double click to select word */
1778 Line l
= selgetlinebyy(term
->sel
.ey
);
1781 //FIXME: write better word selection code
1782 term
->sel
.bx
= term
->sel
.ex
;
1783 if (IS_GFX(l
[term
->sel
.bx
].mode
)) {
1784 while (term
->sel
.bx
> 0 && IS_GFX(l
[term
->sel
.bx
-1].mode
)) --term
->sel
.bx
;
1785 term
->sel
.b
.x
= term
->sel
.bx
;
1786 while (term
->sel
.ex
< term
->col
-1 && IS_GFX(l
[term
->sel
.ex
+1].mode
)) ++term
->sel
.ex
;
1788 while (term
->sel
.bx
> 0 && !IS_GFX(l
[term
->sel
.bx
-1].mode
) && l
[term
->sel
.bx
-1].c
[0] != ' ') --term
->sel
.bx
;
1789 term
->sel
.b
.x
= term
->sel
.bx
;
1790 while (term
->sel
.ex
< term
->col
-1 && !IS_GFX(l
[term
->sel
.ex
+1].mode
) && l
[term
->sel
.ex
+1].c
[0] != ' ') ++term
->sel
.ex
;
1792 term
->sel
.e
.x
= term
->sel
.ex
;
1793 term
->sel
.b
.y
= term
->sel
.e
.y
= term
->sel
.ey
;
1800 // multiline or multichar selection
1804 term
->sel
.tclick2
= term
->sel
.tclick1
;
1805 term
->sel
.tclick1
= mclock_ticks();
1810 static void bmotion (XEvent
*e
) {
1811 if (term
== NULL
) return;
1813 switch (opt_tabposition
) {
1815 if (e
->xbutton
.y
>= xw
.h
-xw
.tabheight
) return;
1818 if (e
->xbutton
.y
< xw
.tabheight
) return;
1822 if (term
->sel
.mode
) {
1823 int oldey
= term
->sel
.ey
, oldex
= term
->sel
.ex
;
1825 getbuttoninfo(e
, NULL
, &term
->sel
.ex
, &term
->sel
.ey
); // this sets sel.b and sel.e
1826 if (oldey
!= term
->sel
.ey
|| oldex
!= term
->sel
.ex
) {
1827 int starty
= MIN(oldey
, term
->sel
.ey
);
1828 int endy
= MAX(oldey
, term
->sel
.ey
);
1830 tsetdirt(starty
, endy
);
1835 //if (IS_SET(MODE_MOUSE) && e->xbutton.button != 0) mousereport(e);
1839 ////////////////////////////////////////////////////////////////////////////////
1841 static inline void setWantRedraw (void) { if (term
!= NULL
) term
->wantRedraw
= 1; }
1845 static void dump (char c) {
1848 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
1849 if (++col % 10 == 0) fprintf(stderr, "\n");
1854 static __attribute__((noreturn
)) void execsh (const char *str
) {
1858 char *envshell
= getenv("SHELL");
1860 DEFAULT(envshell
, opt_shell
);
1861 setenv("TERM", opt_term
, 1);
1862 args
= opt_cmd
? opt_cmd
: (char *[]){envshell
, "-i", NULL
};
1866 args
= calloc(32768, sizeof(char *));
1867 if (args
== NULL
) exit(EXIT_FAILURE
);
1871 while (*str
&& isspace(*str
)) ++str
;
1875 while (*str
&& !isspace(*str
)) {
1876 if (*str
++ == '\\') {
1881 args
[argc
] = calloc(str
-b
+1, 1);
1882 memcpy(args
[argc
], b
, str
-b
);
1885 FILE *fo = fopen("z.log", "a");
1886 fprintf(fo, "%d: [%s]\n", argc, args[argc]);
1892 if (argc
< 1) exit(EXIT_FAILURE
);
1894 execvp(args
[0], args
);
1899 static int ttynew (Term
*term
) {
1901 struct winsize w
= {term
->row
, term
->col
, 0, 0};
1902 static int signalset
= 0;
1904 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0) die("openpty failed: %s", SERRNO
);
1908 switch (term
->pid
= fork()) {
1909 case -1: /* error */
1910 fprintf(stderr
, "fork failed");
1913 setsid(); /* create a new process group */
1914 dup2(s
, STDIN_FILENO
);
1915 dup2(s
, STDOUT_FILENO
);
1916 dup2(s
, STDERR_FILENO
);
1917 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0) die("ioctl TIOCSCTTY failed: %s", SERRNO
);
1920 execsh(term
->execcmd
);
1922 default: /* master */
1927 if (!signalset
) { signalset
= 1; signal(SIGCHLD
, sigchld
); }
1934 ////////////////////////////////////////////////////////////////////////////////
1936 static int ttycanread (void) {
1939 struct timeval timeout
= {0};
1941 if (term
->dead
|| term
->cmdfd
< 0) return 0;
1943 FD_SET(term
->cmdfd
, &rfd
);
1944 if (select(term
->cmdfd
+1, &rfd
, NULL
, NULL
, &timeout
) < 0) {
1945 if (errno
== EINTR
) continue;
1946 die("select failed: %s", SERRNO
);
1948 if (FD_ISSET(term
->cmdfd
, &rfd
)) return 1;
1955 static int ttycanwrite (void) {
1958 struct timeval timeout
= {0};
1960 if (term
->dead
|| term
->cmdfd
< 0) return 0;
1962 FD_SET(term
->cmdfd
, &wfd
);
1963 if (select(term
->cmdfd
+1, NULL
, &wfd
, NULL
, &timeout
) < 0) {
1964 if (errno
== EINTR
) continue;
1965 die("select failed: %s", SERRNO
);
1967 if (FD_ISSET(term
->cmdfd
, &wfd
)) return 1;
1975 static void wrstr (const char *s
, int len
) {
1976 if (s
== NULL
) return;
1978 unsigned char c
= (unsigned char)(*s
++);
1980 if (c
< 32) fprintf(stderr
, "{%u}", c
); else fwrite(&c
, 1, 1, stderr
);
1986 static void ttyread (void) {
1990 /* append read bytes to unprocessed bytes */
1991 if (term
== NULL
|| term
->dead
|| term
->cmdfd
< 0) return;
1992 #ifdef DUMP_PROG_OUTPUT
1993 term
->xobuflen
= term
->obuflen
;
1995 left
= OBUFSIZ
-term
->obuflen
;
1996 //dlogf("0: ttyread before: free=%d, used=%d", left, term->obuflen);
1997 while (left
> 0 && ttycanread()) {
2000 //if ((ret = read(term->cmdfd, term->obuf+term->obuflen, 1)) < 0) die("Couldn't read from shell: %s", SERRNO);
2001 if ((ret
= read(term
->cmdfd
, term
->obuf
+term
->obuflen
, left
)) < 0) {
2002 //fprintf(stderr, "Warning: couldn't read from tty #%d: %s\n", termidx, SERRNO);
2005 term
->obuflen
+= ret
;
2008 //dlogf("1: ttyread after: free=%d, used=%d", left, term->obuflen);
2009 /* process every complete utf8 char */
2010 #ifdef DUMP_PROG_OUTPUT
2012 FILE *fo
= fopen("zlogo.log", "ab");
2014 fwrite(term
->obuf
+term
->xobuflen
, term
->obuflen
-term
->xobuflen
, 1, fo
);
2020 if (term
->needConv
) {
2021 // need conversion from locale to utf-8
2022 //fprintf(stderr, "buf: %d bytes\n", term->obuflen);
2023 while (term
->obuflen
> 0) {
2024 char obuf
[UTF_SIZ
+1];
2027 len
= loc2utf(obuf
, ptr
, 1);
2030 fprintf(stderr
, "rdc: [");
2032 fprintf(stderr
, "] --> [");
2034 fprintf(stderr
, "]\n");
2047 // don't do any conversion
2048 while (term
->obuflen
>= UTF_SIZ
|| isfullutf8(ptr
, term
->obuflen
)) {
2051 int charsize
= utf8decode(ptr
, &utf8c
); /* returns size of utf8 char in bytes */
2054 len
= utf8encode(utf8c
, s
);
2057 fprintf(stderr
, "rdx: [");
2059 fprintf(stderr
, "]\n");
2068 term
->obuflen
-= charsize
;
2070 //dlogf("2: ttyread afterproc: used=%d", term->obuflen);
2072 /* keep any uncomplete utf8 char for the next call */
2073 if (term
->obuflen
> 0) memmove(term
->obuf
, ptr
, term
->obuflen
);
2077 static void ttyflushwrbuf (void) {
2078 if (term
== NULL
|| term
->dead
|| term
->cmdfd
< 0) return;
2079 if (term
->wrbufpos
>= term
->wrbufused
) {
2080 term
->wrbufpos
= term
->wrbufused
= 0;
2083 //dlogf("0: ttyflushwrbuf before: towrite=%d", term->wrbufused-term->wrbufpos);
2084 while (term
->wrbufpos
< term
->wrbufused
&& ttycanwrite()) {
2087 if ((ret
= write(term
->cmdfd
, term
->wrbuf
+term
->wrbufpos
, term
->wrbufused
-term
->wrbufpos
)) == -1) {
2088 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2090 term
->wrbufpos
+= ret
;
2092 if (term
->wrbufpos
> 0) {
2093 int left
= term
->wrbufused
-term
->wrbufpos
;
2096 // write buffer is empty
2097 term
->wrbufpos
= term
->wrbufused
= 0;
2099 memmove(term
->wrbuf
, term
->wrbuf
+term
->wrbufpos
, left
);
2101 term
->wrbufused
= left
;
2104 //dlogf("1: ttyflushwrbuf after: towrite=%d", term->wrbufused-term->wrbufpos);
2108 // convert char to locale and write it
2109 static void ttywriterawchar (const char *s
, int len
) {
2113 if (s
== NULL
|| len
< 1) return;
2114 if (term
->needConv
) {
2115 if ((clen
= utf2loc(loc
, s
, len
)) < 1) return;
2117 if ((clen
= utf8size(s
)) < 1) return;
2118 memmove(loc
, s
, clen
);
2120 #ifdef DUMP_IO_WRITE
2122 fprintf(stderr
, "wrc: [");
2124 fprintf(stderr
, "] --> [");
2126 fprintf(stderr
, "]\n");
2131 while (term
->wrbufused
+clen
>= term
->wrbufsize
) {
2132 //FIXME: make write buffer dynamic?
2133 // force write at least one char
2134 //dlogf("ttywrite: forced write");
2135 if (write(term
->cmdfd
, term
->wrbuf
+term
->wrbufpos
, 1) == -1) {
2136 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2140 ttyflushwrbuf(); // make room for char
2142 memcpy(term
->wrbuf
+term
->wrbufused
, loc
, clen
);
2143 term
->wrbufused
+= clen
;
2147 static void ttywrite (const char *s
, size_t n
) {
2148 if (term
== NULL
|| term
->dead
|| term
->cmdfd
< 0) return;
2149 #ifdef DUMP_PROG_INPUT
2150 if (s
!= NULL
&& n
> 0) {
2151 FILE *fo
= fopen("zlogw.log", "ab");
2153 fwrite(s
, n
, 1, fo
);
2159 if (s
!= NULL
&& n
> 0) {
2161 unsigned char c
= (unsigned char)(s
[0]);
2163 if (term
->ubufpos
> 0 && isfullutf8(term
->ubuf
, term
->ubufpos
)) {
2164 // have complete char
2165 ttywriterawchar(term
->ubuf
, term
->ubufpos
);
2170 if (term
->ubufpos
== 0) {
2173 ttywriterawchar(s
, 1);
2174 } else if ((c
&0xc0) == 0xc0) {
2176 term
->ubuf
[term
->ubufpos
++] = *s
;
2178 // ignore unsynced utf-8
2185 if (c
< 128 || term
->ubufpos
>= UTF_SIZ
|| (c
&0xc0) == 0xc0) {
2186 // discard previous utf-8, it's bad
2191 term
->ubuf
[term
->ubufpos
++] = *s
;
2194 if (isfullutf8(term
->ubuf
, term
->ubufpos
)) {
2195 // have complete char
2196 ttywriterawchar(term
->ubuf
, term
->ubufpos
);
2205 ////////////////////////////////////////////////////////////////////////////////
2207 static void ttyresize (void) {
2210 if (term
!= NULL
&& term
->cmdfd
>= 0) {
2211 w
.ws_row
= term
->row
;
2212 w
.ws_col
= term
->col
;
2213 w
.ws_xpixel
= w
.ws_ypixel
= 0;
2214 if (ioctl(term
->cmdfd
, TIOCSWINSZ
, &w
) < 0) fprintf(stderr
, "Warning: couldn't set window size: %s\n", SERRNO
);
2220 ////////////////////////////////////////////////////////////////////////////////
2222 static void csidump (void) {
2224 for (int f
= 1; f
< term
->escseq
.len
; ++f
) {
2225 uint c
= (term
->escseq
.buf
[f
]&0xff);
2227 if (isprint(c
)) putchar(c
);
2228 else if (c
== '\n') printf("(\\n)");
2229 else if (c
== '\r') printf("(\\r)");
2230 else if (c
== 0x1b) printf("(\\e)");
2231 else printf("(%02x)", c
);
2237 static void tsetdirt (int top
, int bot
) {
2238 LIMIT(top
, 0, term
->row
-1);
2239 LIMIT(bot
, 0, term
->row
-1);
2240 for (int y
= top
; y
<= bot
; ++y
) markDirty(y
, 2);
2244 static void tfulldirt (void) {
2245 tsetdirt(0, term
->row
-1);
2249 static void tmoveto (int x
, int y
) {
2250 LIMIT(x
, 0, term
->col
-1);
2251 LIMIT(y
, 0, term
->row
-1);
2252 term
->c
.state
&= ~CURSOR_WRAPNEXT
;
2253 if (term
->c
.x
!= x
|| term
->c
.y
!= y
) {
2261 static void tclearregion (int x1
, int y1
, int x2
, int y2
) {
2264 if (x1
> x2
) { temp
= x1
; x1
= x2
; x2
= temp
; }
2265 if (y1
> y2
) { temp
= y1
; y1
= y2
; y2
= temp
; }
2266 LIMIT(x1
, 0, term
->col
-1);
2267 LIMIT(x2
, 0, term
->col
-1);
2268 LIMIT(y1
, 0, term
->row
-1);
2269 LIMIT(y2
, 0, term
->row
-1);
2270 for (int y
= y1
; y
<= y2
; ++y
) {
2271 markDirty(y
, (x1
<= 0 && x2
>= term
->col
-1) ? 2 : 1);
2272 for (int x
= x1
; x
<= x2
; ++x
) {
2273 term
->line
[y
][x
].fg
= term
->c
.attr
.fg
;
2274 term
->line
[y
][x
].bg
= term
->c
.attr
.bg
;
2275 term
->line
[y
][x
].state
= GLYPH_DIRTY
;
2276 term
->line
[y
][x
].mode
= ATTR_NULL
|(term
->c
.attr
.mode
&(ATTR_DEFFG
|ATTR_DEFBG
));
2277 term
->line
[y
][x
].c
[0] = ' ';
2278 if (selected(x
, y
)) selhide();
2284 static void tcursor (int mode
) {
2285 if (mode
== CURSOR_SAVE
) {
2286 term
->csaved
= term
->c
;
2287 } else if (mode
== CURSOR_LOAD
) {
2288 term
->c
= term
->csaved
;
2289 tmoveto(term
->c
.x
, term
->c
.y
);
2295 static void treset (void) {
2298 term
->c
= (TCursor
){{
2299 .mode
= ATTR_NULL
|ATTR_DEFFG
|ATTR_DEFBG
,
2302 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
2303 term
->c
.attr
.fg
= term
->deffg
;
2304 term
->c
.attr
.bg
= term
->defbg
;
2306 g
.state
= GLYPH_DIRTY
;
2307 g
.mode
= term
->c
.attr
.mode
;
2308 g
.fg
= term
->c
.attr
.fg
;
2309 g
.bg
= term
->c
.attr
.bg
;
2314 term
->bot
= term
->row
-1;
2315 term
->mode
= MODE_WRAP
/* | MODE_MOUSEBTN*/;
2316 //tclearregion(0, 0, term->col-1, term->row-1);
2317 for (int y
= 0; y
< term
->row
; ++y
) {
2319 for (int x
= 0; x
< term
->col
; ++x
) term
->alt
[y
][x
] = term
->line
[y
][x
] = g
;
2321 for (int y
= term
->row
; y
< term
->linecount
; ++y
) {
2322 for (int x
= 0; x
< term
->col
; ++x
) term
->line
[y
][x
] = g
;
2324 tcursor(CURSOR_SAVE
);
2330 static int tinitialize (int col
, int row
) {
2331 //memset(term, 0, sizeof(Term));
2332 //term->needConv = needConversion ? 1 : 0;
2333 term
->wrbufsize
= WBUFSIZ
;
2334 term
->deffg
= term
->deffg
;
2335 term
->defbg
= term
->defbg
;
2338 term
->dirty
= calloc(term
->row
, sizeof(*term
->dirty
));
2339 term
->maxhistory
= opt_maxhistory
;
2340 term
->linecount
= term
->maxhistory
+term
->row
;
2341 term
->line
= calloc(term
->linecount
, sizeof(Line
));
2342 term
->alt
= calloc(term
->row
, sizeof(Line
));
2343 for (int y
= 0; y
< term
->linecount
; ++y
) term
->line
[y
] = calloc(term
->col
, sizeof(Glyph
));
2344 for (int y
= 0; y
< term
->row
; ++y
) term
->alt
[y
] = calloc(term
->col
, sizeof(Glyph
));
2351 static void tswapscreen (void) {
2353 for (int f
= 0; f
< term
->row
; ++f
) {
2354 Line t
= term
->line
[f
];
2356 term
->line
[f
] = term
->alt
[f
];
2359 term
->mode
^= MODE_ALTSCREEN
;
2364 //FIXME: will not work for history
2365 static void selscroll (int orig
, int n
) {
2366 if (term
->sel
.bx
== -1) return;
2368 if (BETWEEN(term
->sel
.by
, orig
, term
->bot
) || BETWEEN(term
->sel
.ey
, orig
, term
->bot
)) {
2369 if ((term
->sel
.by
+= n
) > term
->bot
|| (term
->sel
.ey
+= n
) < term
->top
) {
2373 if (term
->sel
.by
< term
->top
) {
2374 term
->sel
.by
= term
->top
;
2377 if (term
->sel
.ey
> term
->bot
) {
2378 term
->sel
.ey
= term
->bot
;
2379 term
->sel
.ex
= term
->col
;
2381 term
->sel
.b
.y
= term
->sel
.by
, term
->sel
.b
.x
= term
->sel
.bx
;
2382 term
->sel
.e
.y
= term
->sel
.ey
, term
->sel
.e
.x
= term
->sel
.ex
;
2387 static void tscrolldown (int orig
, int n
) {
2390 LIMIT(n
, 0, term
->bot
-orig
+1);
2391 //fprintf(stderr, "tscrolldown(%d, %d)\n", orig, n);
2392 tclearregion(0, term
->bot
-n
+1, term
->col
-1, term
->bot
);
2393 for (int f
= term
->bot
; f
>= orig
+n
; --f
) {
2394 temp
= term
->line
[f
];
2395 term
->line
[f
] = term
->line
[f
-n
];
2396 term
->line
[f
-n
] = temp
;
2404 static void tscrollup (int orig
, int n
) {
2407 if (term
== NULL
) return;
2408 LIMIT(n
, 0, term
->bot
-orig
+1);
2410 //fprintf(stderr, "tscrollup(%d, %d)\n", orig, n);
2411 if (!IS_SET(MODE_ALTSCREEN
) && n
== 1 && orig
== 0 && term
->bot
>= term
->row
-1 && term
->maxhistory
> 0) {
2412 //FIXME: move selection instead of resetting
2413 Line l
= term
->line
[term
->linecount
-1];
2415 for (int f
= term
->linecount
-1; f
> term
->row
; --f
) term
->line
[f
] = term
->line
[f
-1];
2416 term
->line
[term
->row
] = l
;
2417 for (int x
= 0; x
< term
->col
; ++x
) l
[x
] = term
->line
[0][x
];
2418 if (term
->sel
.clip
!= NULL
) {
2419 //FIXME: scroll selection
2424 tclearregion(0, orig
, term
->col
-1, orig
+n
-1);
2426 for (int f
= orig
; f
<= term
->bot
-n
; ++f
) {
2427 temp
= term
->line
[f
];
2428 term
->line
[f
] = term
->line
[f
+n
];
2429 term
->line
[f
+n
] = temp
;
2433 selscroll(orig
, -n
);
2437 static void tnewline (int first_col
) {
2440 if (y
== term
->bot
) tscrollup(term
->top
, 1); else ++y
;
2441 tmoveto(first_col
? 0 : term
->c
.x
, y
);
2445 static void csiparse (void) {
2446 const char *p
= term
->escseq
.buf
;
2448 term
->escseq
.narg
= 0;
2449 if (*p
== '?') { term
->escseq
.priv
= 1; ++p
; }
2450 while (p
< term
->escseq
.buf
+term
->escseq
.len
) {
2451 int n
= term
->escseq
.arg
[term
->escseq
.narg
];
2453 for (; *p
&& isdigit(*p
); ++p
) n
= n
*10+(p
[0]-'0');
2454 term
->escseq
.arg
[term
->escseq
.narg
] = n
;
2456 if (*p
== ';' && term
->escseq
.narg
+1 < ESC_ARG_SIZ
) {
2457 ++term
->escseq
.narg
;
2460 term
->escseq
.mode
= *p
;
2461 ++term
->escseq
.narg
;
2468 static void tsetchar (const char *c
) {
2470 int rev
= 0, gfx
= 0;
2471 int x
= term
->c
.x
, y
= term
->c
.y
;
2473 if (!term
->needConv
&& unimap
!= NULL
) {
2477 if (cc
>= 0 && cc
<= 65535) {
2478 ushort uc
= unimap
[cc
];
2500 if (rev || gfx || (term->line[y][x].state & GLYPH_SET) == 0 || ATTRCMP(term->line[y][x], term->c.attr)) {
2501 term->line[y][x] = term->c.attr;
2502 if (rev) term->line[y][x].mode ^= ATTR_REVERSE;
2503 if (gfx) term->line[y][x].mode |= ATTR_GFX;
2505 int clen = utf8size(c), olen = utf8size(term->line[y][x].c);
2507 if (clen < 1 || (clen == olen && memcmp(c, term->line[y][x].c, clen) == 0)) return;
2512 term
->line
[y
][x
] = term
->c
.attr
;
2513 if (rev
) term
->line
[y
][x
].mode
^= ATTR_REVERSE
;
2515 term
->line
[y
][x
].mode
&= ~(ATTR_GFX
|ATTR_GFX1
);
2516 term
->line
[y
][x
].mode
|= (term
->line
[y
][x
].mode
&ATTR_G1
) ? ATTR_GFX1
: ATTR_GFX
;
2519 term
->line
[y
][x
].state
|= (GLYPH_SET
| GLYPH_DIRTY
);
2520 memmove(term
->line
[y
][x
].c
, c
, UTF_SIZ
);
2522 if (IS_GFX(term
->line
[y
][x
].mode
)) {
2523 unsigned char c
= (unsigned char)(term
->line
[y
][x
].c
[0]);
2525 if (c
> 95 && c
< 128) term
->line
[y
][x
].c
[0] -= 95;
2526 else if (c
> 127) term
->line
[y
][x
].c
[0] = ' ';
2528 if (term
->sel
.clip
!= NULL
&& selected(x
, y
)) {
2531 //dlogf("tsetchar(%d,%d): [%c] (%d); dirty=%d\n", x, y, c[0], term->wantRedraw, term->dirty[y]);
2535 static void tdeletechar (int n
) {
2536 int src
= term
->c
.x
+n
;
2537 int dst
= term
->c
.x
;
2538 int size
= term
->col
-src
;
2540 markDirty(term
->c
.y
, 2);
2541 if (src
>= term
->col
) {
2542 tclearregion(term
->c
.x
, term
->c
.y
, term
->col
-1, term
->c
.y
);
2544 memmove(&term
->line
[term
->c
.y
][dst
], &term
->line
[term
->c
.y
][src
], size
*sizeof(Glyph
));
2545 tclearregion(term
->col
-n
, term
->c
.y
, term
->col
-1, term
->c
.y
);
2550 static void tinsertblank (int n
) {
2551 int src
= term
->c
.x
;
2553 int size
= term
->col
-dst
;
2555 markDirty(term
->c
.y
, 2);
2556 if (dst
>= term
->col
) {
2557 tclearregion(term
->c
.x
, term
->c
.y
, term
->col
-1, term
->c
.y
);
2559 memmove(&term
->line
[term
->c
.y
][dst
], &term
->line
[term
->c
.y
][src
], size
*sizeof(Glyph
));
2560 tclearregion(src
, term
->c
.y
, dst
-1, term
->c
.y
);
2565 static void tinsertblankline (int n
) {
2566 if (term
->c
.y
< term
->top
|| term
->c
.y
> term
->bot
) return;
2567 tscrolldown(term
->c
.y
, n
);
2571 static void tdeleteline (int n
) {
2572 if (term
->c
.y
< term
->top
|| term
->c
.y
> term
->bot
) return;
2573 tscrollup(term
->c
.y
, n
);
2577 static void tsetattr (int *attr
, int l
) {
2578 for (int f
= 0; f
< l
; ++f
) {
2581 term
->c
.attr
.mode
&= ~(ATTR_REVERSE
| ATTR_UNDERLINE
| ATTR_BOLD
);
2582 term
->c
.attr
.mode
|= ATTR_DEFFG
| ATTR_DEFBG
;
2583 term
->c
.attr
.fg
= term
->deffg
;
2584 term
->c
.attr
.bg
= term
->defbg
;
2587 term
->c
.attr
.mode
|= ATTR_BOLD
;
2590 term
->c
.attr
.mode
|= ATTR_UNDERLINE
;
2593 term
->c
.attr
.mode
|= ATTR_REVERSE
;
2596 term
->c
.attr
.mode
&= ~ATTR_BOLD
;
2599 term
->c
.attr
.mode
&= ~ATTR_UNDERLINE
;
2602 term
->c
.attr
.mode
&= ~ATTR_REVERSE
;
2605 if (f
+2 < l
&& attr
[f
+1] == 5) {
2607 if (BETWEEN(attr
[f
], 0, 255)) {
2608 term
->c
.attr
.fg
= attr
[f
];
2609 term
->c
.attr
.mode
&= ~ATTR_DEFFG
;
2611 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[f
]);
2614 //fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2615 term
->c
.attr
.fg
= term
->deffg
;
2616 term
->c
.attr
.mode
|= ATTR_DEFFG
;
2620 term
->c
.attr
.fg
= term
->deffg
;
2621 term
->c
.attr
.mode
|= ATTR_DEFFG
;
2624 if (f
+2 < l
&& attr
[f
+1] == 5) {
2626 if (BETWEEN(attr
[f
], 0, 255)) {
2627 term
->c
.attr
.bg
= attr
[f
];
2628 term
->c
.attr
.mode
&= ~ATTR_DEFBG
;
2630 fprintf(stderr
, "erresc: bad bgcolor %d\n", attr
[f
]);
2633 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[f
]);
2637 term
->c
.attr
.bg
= term
->defbg
;
2638 term
->c
.attr
.mode
|= ATTR_DEFBG
;
2641 if (BETWEEN(attr
[f
], 30, 37)) { term
->c
.attr
.fg
= attr
[f
]-30; term
->c
.attr
.mode
&= ~ATTR_DEFFG
; }
2642 else if (BETWEEN(attr
[f
], 40, 47)) { term
->c
.attr
.bg
= attr
[f
]-40; term
->c
.attr
.mode
&= ~ATTR_DEFBG
; }
2643 else if (BETWEEN(attr
[f
], 90, 97)) { term
->c
.attr
.fg
= attr
[f
]-90+8; term
->c
.attr
.mode
&= ~ATTR_DEFFG
; }
2644 else if (BETWEEN(attr
[f
], 100, 107)) { term
->c
.attr
.bg
= attr
[f
]-100+8; term
->c
.attr
.mode
&= ~ATTR_DEFBG
; }
2645 else { fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[f
]); csidump(); }
2652 static void tsetscroll (int t
, int b
) {
2655 LIMIT(t
, 0, term
->row
-1);
2656 LIMIT(b
, 0, term
->row
-1);
2667 ////////////////////////////////////////////////////////////////////////////////
2669 static void csihandle (void) {
2670 switch (term
->escseq
.mode
) {
2671 case '@': /* ICH -- Insert <n> blank char */
2672 DEFAULT(term
->escseq
.arg
[0], 1);
2673 tinsertblank(term
->escseq
.arg
[0]);
2675 case 'A': /* CUU -- Cursor <n> Up */
2677 DEFAULT(term
->escseq
.arg
[0], 1);
2678 tmoveto(term
->c
.x
, term
->c
.y
-term
->escseq
.arg
[0]);
2680 case 'B': /* CUD -- Cursor <n> Down */
2681 DEFAULT(term
->escseq
.arg
[0], 1);
2682 tmoveto(term
->c
.x
, term
->c
.y
+term
->escseq
.arg
[0]);
2684 case 'C': /* CUF -- Cursor <n> Forward */
2686 DEFAULT(term
->escseq
.arg
[0], 1);
2687 tmoveto(term
->c
.x
+term
->escseq
.arg
[0], term
->c
.y
);
2689 case 'D': /* CUB -- Cursor <n> Backward */
2690 DEFAULT(term
->escseq
.arg
[0], 1);
2691 tmoveto(term
->c
.x
-term
->escseq
.arg
[0], term
->c
.y
);
2693 case 'E': /* CNL -- Cursor <n> Down and first col */
2694 DEFAULT(term
->escseq
.arg
[0], 1);
2695 tmoveto(0, term
->c
.y
+term
->escseq
.arg
[0]);
2697 case 'F': /* CPL -- Cursor <n> Up and first col */
2698 DEFAULT(term
->escseq
.arg
[0], 1);
2699 tmoveto(0, term
->c
.y
-term
->escseq
.arg
[0]);
2701 case 'G': /* CHA -- Move to <col> */
2702 case '`': /* XXX: HPA -- same? */
2703 DEFAULT(term
->escseq
.arg
[0], 1);
2704 tmoveto(term
->escseq
.arg
[0]-1, term
->c
.y
);
2706 case 'H': /* CUP -- Move to <row> <col> */
2707 case 'f': /* XXX: HVP -- same? */
2708 DEFAULT(term
->escseq
.arg
[0], 1);
2709 DEFAULT(term
->escseq
.arg
[1], 1);
2710 tmoveto(term
->escseq
.arg
[1]-1, term
->escseq
.arg
[0]-1);
2712 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
2713 case 'J': /* ED -- Clear screen */
2715 switch (term
->escseq
.arg
[0]) {
2717 tclearregion(term
->c
.x
, term
->c
.y
, term
->col
-1, term
->c
.y
);
2718 if (term
->c
.y
< term
->row
-1) tclearregion(0, term
->c
.y
+1, term
->col
-1, term
->row
-1);
2721 if (term
->c
.y
> 1) tclearregion(0, 0, term
->col
-1, term
->c
.y
-1);
2722 tclearregion(0, term
->c
.y
, term
->c
.x
, term
->c
.y
);
2725 tclearregion(0, 0, term
->col
-1, term
->row
-1);
2731 case 'K': /* EL -- Clear line */
2732 switch (term
->escseq
.arg
[0]) {
2734 tclearregion(term
->c
.x
, term
->c
.y
, term
->col
-1, term
->c
.y
);
2737 tclearregion(0, term
->c
.y
, term
->c
.x
, term
->c
.y
);
2740 tclearregion(0, term
->c
.y
, term
->col
-1, term
->c
.y
);
2744 case 'S': /* SU -- Scroll <n> line up */
2745 DEFAULT(term
->escseq
.arg
[0], 1);
2746 tscrollup(term
->top
, term
->escseq
.arg
[0]);
2748 case 'T': /* SD -- Scroll <n> line down */
2749 DEFAULT(term
->escseq
.arg
[0], 1);
2750 tscrolldown(term
->top
, term
->escseq
.arg
[0]);
2752 case 'L': /* IL -- Insert <n> blank lines */
2753 DEFAULT(term
->escseq
.arg
[0], 1);
2754 tinsertblankline(term
->escseq
.arg
[0]);
2756 case 'l': /* RM -- Reset Mode */
2757 if (term
->escseq
.priv
) {
2758 switch (term
->escseq
.arg
[0]) {
2759 case 1: // 1001 for xterm compatibility
2760 DUMP_KEYPAD_SWITCH("1", "OFF");
2761 term
->mode
&= ~MODE_APPKEYPAD
;
2763 case 5: /* DECSCNM -- Remove reverse video */
2764 if (IS_SET(MODE_REVERSE
)) {
2765 term
->mode
&= ~MODE_REVERSE
;
2769 case 7: /* autowrap off */
2770 term
->mode
&= ~MODE_WRAP
;
2772 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
2774 case 20: /* non-standard code? */
2775 term
->mode
&= ~MODE_CRLF
;
2777 case 25: /* hide cursor */
2778 if ((term
->c
.state
&CURSOR_HIDE
) == 0) {
2779 term
->c
.state
|= CURSOR_HIDE
;
2780 term
->wantRedraw
= 1;
2783 case 1000: /* disable X11 xterm mouse reporting */
2784 term
->mode
&= ~MODE_MOUSEBTN
;
2787 term
->mode
&= ~MODE_MOUSEMOTION
;
2790 term
->mode
&= ~MODE_FOCUSEVT
;
2792 case 1049: /* = 1047 and 1048 */
2795 if (IS_SET(MODE_ALTSCREEN
)) {
2796 tclearregion(0, 0, term
->col
-1, term
->row
-1);
2799 if (term
->escseq
.arg
[0] != 1049) break;
2801 tcursor(CURSOR_LOAD
);
2803 case 2004: /* reset bracketed paste mode */
2804 term
->mode
&= ~MODE_BRACPASTE
;
2810 switch (term
->escseq
.arg
[0]) {
2812 term
->mode
&= ~MODE_DISPCTRL
;
2815 term
->mode
&= ~MODE_INSERT
;
2822 case 'M': /* DL -- Delete <n> lines */
2823 DEFAULT(term
->escseq
.arg
[0], 1);
2824 tdeleteline(term
->escseq
.arg
[0]);
2826 case 'X': /* ECH -- Erase <n> char */
2827 DEFAULT(term
->escseq
.arg
[0], 1);
2828 tclearregion(term
->c
.x
, term
->c
.y
, term
->c
.x
+ term
->escseq
.arg
[0], term
->c
.y
);
2830 case 'P': /* DCH -- Delete <n> char */
2831 DEFAULT(term
->escseq
.arg
[0], 1);
2832 tdeletechar(term
->escseq
.arg
[0]);
2834 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
2835 case 'd': /* VPA -- Move to <row> */
2836 DEFAULT(term
->escseq
.arg
[0], 1);
2837 tmoveto(term
->c
.x
, term
->escseq
.arg
[0]-1);
2839 case 'h': /* SM -- Set terminal mode */
2840 if (term
->escseq
.priv
) {
2841 switch (term
->escseq
.arg
[0]) {
2843 DUMP_KEYPAD_SWITCH("1", "ON");
2844 term
->mode
|= MODE_APPKEYPAD
;
2846 case 5: /* DECSCNM -- Reverve video */
2847 if (!IS_SET(MODE_REVERSE
)) {
2848 term
->mode
|= MODE_REVERSE
;
2853 term
->mode
|= MODE_WRAP
;
2856 term
->mode
|= MODE_CRLF
;
2858 case 12: /* att610 -- Start blinking cursor (IGNORED) */
2859 /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
2860 if (term
->escseq
.narg
> 1 && term
->escseq
.arg
[1] != 25) break;
2862 if ((term
->c
.state
&CURSOR_HIDE
) != 0) {
2863 term
->c
.state
&= ~CURSOR_HIDE
;
2864 term
->wantRedraw
= 1;
2867 case 1000: /* 1000,1002: enable xterm mouse report */
2868 term
->mode
|= MODE_MOUSEBTN
;
2871 term
->mode
|= MODE_MOUSEMOTION
;
2874 term
->mode
|= MODE_FOCUSEVT
;
2876 case 1049: /* = 1047 and 1048 */
2879 if (IS_SET(MODE_ALTSCREEN
)) tclearregion(0, 0, term
->col
-1, term
->row
-1); else tswapscreen();
2880 if (term
->escseq
.arg
[0] != 1049) break;
2882 tcursor(CURSOR_SAVE
);
2884 case 2004: /* set bracketed paste mode */
2885 term
->mode
|= MODE_BRACPASTE
;
2887 default: goto unknown
;
2890 switch (term
->escseq
.arg
[0]) {
2892 term
->mode
|= MODE_DISPCTRL
;
2895 term
->mode
|= MODE_INSERT
;
2902 case 'm': /* SGR -- Terminal attribute (color) */
2903 tsetattr(term
->escseq
.arg
, term
->escseq
.narg
);
2906 if (!term
->escseq
.priv
) {
2907 switch (term
->escseq
.arg
[0]) {
2908 case 6: { /* cursor position report */
2911 sprintf(buf
, "\x1b[%d;%dR", term
->c
.x
+1, term
->c
.y
+1);
2917 case 'r': /* DECSTBM -- Set Scrolling Region */
2918 if (term
->escseq
.priv
&& term
->escseq
.arg
[0] == 1001) {
2919 // xterm compatibility
2920 DUMP_KEYPAD_SWITCH("1001", "OFF");
2921 term
->mode
&= ~MODE_APPKEYPAD
;
2922 } else if (term
->escseq
.priv
) {
2925 DEFAULT(term
->escseq
.arg
[0], 1);
2926 DEFAULT(term
->escseq
.arg
[1], term
->row
);
2927 tsetscroll(term
->escseq
.arg
[0]-1, term
->escseq
.arg
[1]-1);
2931 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
2932 if (term
->escseq
.priv
&& term
->escseq
.arg
[0] == 1001) {
2933 // xterm compatibility
2934 DUMP_KEYPAD_SWITCH("1001", "ON");
2935 term
->mode
|= MODE_APPKEYPAD
;
2937 tcursor(CURSOR_SAVE
);
2940 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
2941 tcursor(CURSOR_LOAD
);
2945 fprintf(stderr
, "erresc: unknown csi ");
2952 static void csireset (void) {
2953 memset(&term
->escseq
, 0, sizeof(term
->escseq
));
2957 static void tputtab (void) {
2958 int space
= opt_tabsize
-term
->c
.x
%opt_tabsize
;
2960 if (space
> 0) tmoveto(term
->c
.x
+space
, term
->c
.y
);
2964 ////////////////////////////////////////////////////////////////////////////////
2965 // put char to output buffer or process command
2967 // return 1 if this was control character
2968 // return -1 if this should break esape sequence
2969 static int tputc_ctrl (char ascii
) {
2972 if (term
->esc
&ESC_TITLE
) return 0;
2975 case '\t': tputtab(); break;
2976 case '\b': tmoveto(term
->c
.x
-1, term
->c
.y
); break;
2977 case '\r': tmoveto(0, term
->c
.y
); break;
2978 case '\f': case '\n': case '\v': tnewline(IS_SET(MODE_CRLF
)); break; /* go to first col if the mode is set */
2979 case '\a': if (!(xw
.state
& WIN_FOCUSED
)) xseturgency(1); if (term
->audiblebell
) XBell(xw
.dpy
, 100); break;
2980 case 14: term
->c
.attr
.mode
|= ATTR_G1
; break;
2981 case 15: term
->c
.attr
.mode
&= ~ATTR_G1
; break;
2982 case 0x18: case 0x1a: res
= -1; break; // do nothing, interrupt current escape sequence
2983 case 127: break; // ignore it
2984 case '\033': csireset(); term
->esc
= ESC_START
; break;
2985 //case 0x9b: csireset(); term->esc = ESC_START | ESC_CSI; break;
2986 default: res
= 0; break;
2992 static void tputc (const char *c
) {
2994 int ctl
= tputc_ctrl(ascii
);
2996 if (ctl
> 0) return; // control char; should not break escape sequence
2998 // control char; should break escape sequence
3002 //dlogf("tputc: [%c]\n", c[0]);
3003 if (term
->esc
& ESC_START
) {
3004 if (term
->esc
& ESC_CSI
) {
3005 term
->escseq
.buf
[term
->escseq
.len
++] = ascii
;
3006 if (BETWEEN(ascii
, 0x40, 0x7E) || term
->escseq
.len
>= ESC_BUF_SIZ
) {
3011 } else if (term
->esc
& ESC_OSC
) {
3012 /* TODO: handle other OSC */
3016 term
->esc
= ESC_START
| ESC_TITLE
;
3019 } else if (term
->esc
& ESC_TITLE
) {
3020 int len
= utf8size(c
);
3022 if (ascii
== '\a' || term
->titlelen
+len
>= ESC_TITLE_SIZ
) {
3024 term
->title
[term
->titlelen
] = '\0';
3025 fixWindowTitle(term
);
3027 } else if (len
> 0) {
3028 memcpy(term
->title
+term
->titlelen
, c
, len
);
3029 term
->titlelen
+= len
;
3030 term
->title
[term
->titlelen
] = '\0';
3032 } else if (term
->esc
& ESC_ALTCHARSET
) {
3035 case '0': /* Line drawing crap */
3036 term
->c
.attr
.mode
|= ATTR_GFX
;
3038 case 'B': /* Back to regular text */
3039 term
->c
.attr
.mode
&= ~ATTR_GFX
;
3042 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
3043 term
->c
.attr
.mode
&= ~ATTR_GFX
;
3046 } else if (term
->esc
& ESC_ALTG1
) {
3049 case '0': /* Line drawing crap */
3050 term
->c
.attr
.mode
|= ATTR_GFX1
;
3052 case 'B': /* Back to regular text */
3053 term
->c
.attr
.mode
&= ~ATTR_GFX1
;
3056 fprintf(stderr
, "esc unhandled charset: ESC ) %c\n", ascii
);
3057 term
->c
.attr
.mode
&= ~ATTR_GFX1
;
3060 } else if (term
->esc
& ESC_HASH
) {
3063 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
3064 //tfillscreenwithE();
3067 } else if (term
->esc
& ESC_PERCENT
) {
3071 case '[': term
->esc
|= ESC_CSI
; break;
3072 case ']': term
->esc
|= ESC_OSC
; break;
3073 case '(': term
->esc
|= ESC_ALTCHARSET
; break;
3074 case ')': term
->esc
|= ESC_ALTG1
; break;
3075 case '#': term
->esc
|= ESC_HASH
; break;
3076 case '%': term
->esc
|= ESC_PERCENT
; break;
3077 case 'D': /* IND -- Linefeed */
3079 if (term
->c
.y
== term
->bot
) tscrollup(term
->top
, 1); else tmoveto(term
->c
.x
, term
->c
.y
+1);
3081 case 'E': /* NEL -- Next line */
3083 tnewline(1); /* always go to first col */
3085 case 'M': /* RI -- Reverse linefeed */
3087 if (term
->c
.y
== term
->top
) tscrolldown(term
->top
, 1); else tmoveto(term
->c
.x
, term
->c
.y
-1);
3089 case 'c': /* RIS -- Reset to inital state */
3093 case '=': /* DECPAM -- Application keypad */
3094 DUMP_KEYPAD_SWITCH("=", "ON");
3096 term
->mode
|= MODE_APPKEYPAD
;
3098 case '>': /* DECPNM -- Normal keypad */
3099 DUMP_KEYPAD_SWITCH(">", "OFF");
3101 term
->mode
&= ~MODE_APPKEYPAD
;
3103 case '7': /* DECSC -- Save Cursor */
3104 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
3107 tcursor(CURSOR_SAVE
);
3109 case '8': /* DECRC -- Restore Cursor */
3112 tcursor(CURSOR_LOAD
);
3114 case 'Z': /* DEC private identification */
3116 ttywritestr("\x1b[?1;2c");
3120 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X ('%c')\n", (uchar
)ascii
, isprint(ascii
)?ascii
:'.');
3125 //if (term->sel.bx != -1 && BETWEEN(term->c.y, term->sel.by, term->sel.ey)) term->sel.bx = -1;
3127 if (term
->needConv
&& IS_GFX(term
->c
.attr
.mode
)) {
3131 if (cc
< 32 || cc
>= 127) break; //FIXME: nothing at all
3133 if ((unsigned char)ascii
< 32 || ascii
== 127) break; // seems that this chars are empty too
3135 if (IS_SET(MODE_WRAP
) && (term
->c
.state
&CURSOR_WRAPNEXT
)) tnewline(1); // always go to first col
3137 if (term
->c
.x
+1 < term
->col
) tmoveto(term
->c
.x
+1, term
->c
.y
); else term
->c
.state
|= CURSOR_WRAPNEXT
;
3143 static void tunshowhistory (void) {
3144 if (term
!= NULL
&& term
->topline
!= 0) {
3146 term
->wantRedraw
= 1;
3147 term
->lastDrawTime
= 0;
3152 static void tsendfocusevent (int focused
) {
3153 if (term
!= NULL
&& IS_SET(MODE_FOCUSEVT
)) {
3154 ttywritestr("\x1b[");
3155 ttywrite(focused
?"I":"O", 1);
3160 static void tcmdlinedirty (void) {
3162 markDirty(term
->row
-term
->topline
-1, 2);
3163 term
->wantRedraw
= 1;
3168 static void tcmdlinefixofs (void) {
3171 len
= utf8strlen(term
->cmdline
);
3172 ofs
= len
-(term
->col
-1);
3173 if (ofs
< 0) ofs
= 0;
3174 for (term
->cmdofs
= 0; ofs
> 0; --ofs
) term
->cmdofs
+= utf8size(term
->cmdline
+term
->cmdofs
);
3179 static void tcmdlinehide (void) {
3180 term
->cmdMode
= CMDMODE_NONE
;
3186 static void tcmdlinemsg (const char *msg
) {
3190 term
->cmdMode
= CMDMODE_MESSAGE
;
3194 int len
= utf8size(msg
);
3196 if (len
< 1 || ofs
+len
>= sizeof(term
->cmdline
)-1) break;
3197 memcpy(term
->cmdline
+ofs
, msg
, len
);
3202 term
->cmdline
[ofs
] = 0;
3208 static void tcmdlineinit (void) {
3209 term
->cmdMode
= CMDMODE_INPUT
;
3211 term
->cmdline
[0] = 0;
3219 static void tcmdaddchar (const char *s
) {
3220 int len
= utf8size(s
);
3223 int slen
= strlen(term
->cmdline
);
3225 if (slen
+len
< sizeof(term
->cmdline
)) {
3226 memcpy(term
->cmdline
+slen
, s
, len
);
3227 term
->cmdline
[slen
+len
] = 0;
3234 static void tcmdput (const char *s
, int len
) {
3238 term
->cmdc
[term
->cmdcl
++] = *s
++;
3239 term
->cmdc
[term
->cmdcl
] = 0;
3241 if ((ok
= isfullutf8(term
->cmdc
, term
->cmdcl
)) != 0 || term
->cmdcl
== UTF_SIZ
) {
3242 if (ok
) tcmdaddchar(term
->cmdc
);
3249 ////////////////////////////////////////////////////////////////////////////////
3251 static int tresize (int col
, int row
) {
3252 int mincol
= MIN(col
, term
->col
);
3253 int slide
= term
->c
.y
-row
+1;
3256 if (col
< 1 || row
< 1) return 0;
3258 g
.state
= GLYPH_DIRTY
;
3259 g
.mode
= ATTR_NULL
|ATTR_DEFFG
|ATTR_DEFBG
;
3266 tsetscroll(0, term
->row
-1);
3267 for (; slide
> 0; --slide
) tscrollup(0, 1); // to fill history
3270 if (row
< term
->row
) {
3271 /* free unneeded rows */
3272 for (int f
= row
; f
< term
->row
; ++f
) free(term
->alt
[f
]);
3273 for (int f
= term
->linecount
-(term
->row
-row
); f
< term
->linecount
; ++f
) free(term
->line
[f
]);
3274 term
->linecount
-= (term
->row
-row
);
3275 /* resize to new height */
3276 term
->alt
= realloc(term
->alt
, row
*sizeof(Line
));
3277 term
->line
= realloc(term
->line
, term
->linecount
*sizeof(Line
));
3278 } else if (row
> term
->row
) {
3279 /* resize to new height */
3280 term
->alt
= realloc(term
->alt
, row
*sizeof(Line
));
3281 term
->line
= realloc(term
->line
, (row
+term
->maxhistory
)*sizeof(Line
));
3282 /* add more lines */
3283 for (int f
= term
->row
; f
< row
; ++f
) {
3284 term
->alt
[f
] = calloc(col
, sizeof(Glyph
));
3285 for (int x
= 0; x
< col
; ++x
) term
->alt
[f
][x
] = g
;
3287 for (int f
= 0; f
< row
-term
->row
; ++f
) {
3288 int y
= term
->linecount
++;
3290 term
->line
[y
] = calloc(col
, sizeof(Glyph
));
3291 for (int x
= 0; x
< col
; ++x
) term
->line
[y
][x
] = g
;
3295 if (row
!= term
->row
) {
3296 term
->dirty
= realloc(term
->dirty
, row
*sizeof(*term
->dirty
));
3299 /* resize each row to new width, zero-pad if needed */
3300 for (int f
= 0; f
< term
->linecount
; ++f
) {
3301 term
->line
[f
] = realloc(term
->line
[f
], col
*sizeof(Glyph
));
3302 for (int x
= mincol
; x
< col
; ++x
) term
->line
[f
][x
] = g
;
3305 term
->alt
[f
] = realloc(term
->alt
[f
], col
*sizeof(Glyph
));
3306 for (int x
= mincol
; x
< col
; ++x
) term
->alt
[f
][x
] = g
;
3309 /* update terminal size */
3313 /* make use of the LIMIT in tmoveto */
3314 tmoveto(term
->c
.x
, term
->c
.y
);
3315 /* reset scrolling region */
3316 tsetscroll(0, row
-1);
3322 static void xresize (int col
, int row
) {
3326 if (term
== NULL
) return;
3327 oldw
= term
->picbufw
;
3328 oldh
= term
->picbufh
;
3329 term
->picbufw
= MAX(1, col
*xw
.cw
);
3330 term
->picbufh
= MAX(1, row
*xw
.ch
);
3331 newbuf
= XCreatePixmap(xw
.dpy
, xw
.win
, term
->picbufw
, term
->picbufh
, XDefaultDepth(xw
.dpy
, xw
.scr
));
3332 XCopyArea(xw
.dpy
, term
->picbuf
, newbuf
, dc
.gc
, 0, 0, term
->picbufw
, term
->picbufh
, 0, 0);
3333 XFreePixmap(xw
.dpy
, term
->picbuf
);
3334 XSetForeground(xw
.dpy
, dc
.gc
, getColor(term
->defbg
));
3335 if (term
->picbufw
> oldw
) {
3336 XFillRectangle(xw
.dpy
, newbuf
, dc
.gc
, oldw
, 0, term
->picbufw
-oldw
, MIN(term
->picbufh
, oldh
));
3337 } else if (term
->picbufw
< oldw
&& xw
.w
> term
->picbufw
) {
3338 XClearArea(xw
.dpy
, xw
.win
, term
->picbufw
, 0, xw
.w
-term
->picbufh
, MIN(term
->picbufh
, oldh
), False
);
3340 if (term
->picbufh
> oldh
) {
3341 XFillRectangle(xw
.dpy
, newbuf
, dc
.gc
, 0, oldh
, term
->picbufw
, term
->picbufh
-oldh
);
3342 } else if (term
->picbufh
< oldh
&& xw
.h
> term
->picbufh
) {
3343 XClearArea(xw
.dpy
, xw
.win
, 0, term
->picbufh
, xw
.w
, xw
.h
-term
->picbufh
, False
);
3345 term
->picbuf
= newbuf
;
3351 ////////////////////////////////////////////////////////////////////////////////
3352 // x11 drawing and utils
3354 static void xcreatebw (void) {
3355 if ((dc.bcol = calloc(MAX_COLOR+1, sizeof(dc.bcol[0]))) == NULL) die("out of memory");
3357 for (int f = 0; f <= MAX_COLOR; ++f) {
3360 nclr = dc.ncol[f].pixel;
3361 XQueryColor(xw.dpy, xw.cmap, &nclr);
3362 fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", f, nclr.red, nclr.green, nclr.blue);
3368 static void xallocbwclr (int idx
, XColor
*color
) {
3371 XQueryColor(xw
.dpy
, xw
.cmap
, color
);
3372 //fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", idx, color->red, color->green, color->blue);
3374 lumi
= 0.3*((double)color
->red
/65535.0)+0.59*((double)color
->green
/65535.0)+0.11*((double)color
->blue
/65535.0);
3375 color
->red
= color
->green
= color
->blue
= (int)(lumi
*65535.0);
3376 if (!XAllocColor(xw
.dpy
, xw
.cmap
, color
)) {
3377 fprintf(stderr
, "WARNING: could not allocate b/w color #%d\n", idx
);
3380 dc
.bcol
[idx
] = color
->pixel
;
3381 color
->red
= color
->blue
= 0;
3382 if (!XAllocColor(xw
.dpy
, xw
.cmap
, color
)) {
3383 fprintf(stderr
, "WARNING: could not allocate b/w color #%d\n", idx
);
3386 dc
.gcol
[idx
] = color
->pixel
;
3390 static void xallocnamedclr (int idx
, const char *cname
) {
3393 if (!XAllocNamedColor(xw
.dpy
, xw
.cmap
, cname
, &color
, &color
)) {
3394 fprintf(stderr
, "WARNING: could not allocate color #%d: '%s'\n", idx
, cname
);
3397 dc
.ncol
[idx
] = color
.pixel
;
3398 xallocbwclr(idx
, &color
);
3402 static void xloadcols (void) {
3405 ulong white
= WhitePixel(xw
.dpy
, xw
.scr
);
3407 if ((dc
.clrs
[0] = dc
.ncol
= calloc(MAX_COLOR
+1, sizeof(dc
.ncol
[0]))) == NULL
) die("out of memory");
3408 if ((dc
.clrs
[1] = dc
.bcol
= calloc(MAX_COLOR
+1, sizeof(dc
.bcol
[0]))) == NULL
) die("out of memory");
3409 if ((dc
.clrs
[2] = dc
.gcol
= calloc(MAX_COLOR
+1, sizeof(dc
.gcol
[0]))) == NULL
) die("out of memory");
3411 for (f
= 0; f
<= MAX_COLOR
; ++f
) dc
.ncol
[f
] = dc
.bcol
[f
] = white
;
3412 /* load colors [0-15] */
3413 for (f
= 0; f
<= 15; ++f
) {
3414 const char *cname
= opt_colornames
[f
]!=NULL
?opt_colornames
[f
]:defcolornames
[f
];
3416 xallocnamedclr(f
, cname
);
3418 /* load colors [256-...] */
3419 for (f
= 256; f
<= MAX_COLOR
; ++f
) {
3420 const char *cname
= opt_colornames
[f
];
3422 if (cname
== NULL
) {
3423 if (LEN(defextcolornames
) <= f
-256) continue;
3424 cname
= defextcolornames
[f
-256];
3426 if (cname
== NULL
) continue;
3427 xallocnamedclr(f
, cname
);
3429 /* load colors [16-255] ; same colors as xterm */
3430 for (f
= 16, r
= 0; r
< 6; ++r
) {
3431 for (g
= 0; g
< 6; ++g
) {
3432 for (b
= 0; b
< 6; ++b
) {
3433 if (opt_colornames
[f
] != NULL
) {
3434 xallocnamedclr(f
, opt_colornames
[f
]);
3436 color
.red
= r
== 0 ? 0 : 0x3737+0x2828*r
;
3437 color
.green
= g
== 0 ? 0 : 0x3737+0x2828*g
;
3438 color
.blue
= b
== 0 ? 0 : 0x3737+0x2828*b
;
3439 if (!XAllocColor(xw
.dpy
, xw
.cmap
, &color
)) {
3440 fprintf(stderr
, "WARNING: could not allocate color #%d\n", f
);
3442 dc
.ncol
[f
] = color
.pixel
;
3443 xallocbwclr(f
, &color
);
3450 for (r
= 0; r
< 24; ++r
, ++f
) {
3451 if (opt_colornames
[f
] != NULL
) {
3452 xallocnamedclr(f
, opt_colornames
[f
]);
3454 color
.red
= color
.green
= color
.blue
= 0x0808+0x0a0a*r
;
3455 if (!XAllocColor(xw
.dpy
, xw
.cmap
, &color
)) {
3456 fprintf(stderr
, "WARNING: could not allocate color #%d\n", f
);
3458 dc
.ncol
[f
] = color
.pixel
;
3459 xallocbwclr(f
, &color
);
3464 for (int f
= 0; f
< LEN(opt_colornames
); ++f
) if (opt_colornames
[f
]) free(opt_colornames
[f
]);
3468 static void xclear (int x1
, int y1
, int x2
, int y2
) {
3469 XSetForeground(xw
.dpy
, dc
.gc
, getColor(IS_SET(MODE_REVERSE
) ? term
->deffg
: term
->defbg
));
3470 XFillRectangle(xw
.dpy
, term
->picbuf
, dc
.gc
, x1
*xw
.cw
, y1
*xw
.ch
, (x2
-x1
+1)*xw
.cw
, (y2
-y1
+1)*xw
.ch
);
3474 static void xhints (void) {
3475 XClassHint
class = {opt_class
, opt_title
};
3476 XWMHints wm
= {.flags
= InputHint
, .input
= 1};
3478 .flags
= PSize
| PResizeInc
| PBaseSize
,
3481 .height_inc
= xw
.ch
,
3483 .base_height
= xw
.h
/*xw.tabheight*/,
3486 //XSetWMNormalHints(xw.dpy, xw.win, &size);
3487 XSetWMProperties(xw
.dpy
, xw
.win
, NULL
, NULL
, NULL
, 0, &size
, &wm
, &class);
3491 static XFontSet
xinitfont (const char *fontstr
) {
3493 char *def
, **missing
;
3497 set
= XCreateFontSet(xw
.dpy
, fontstr
, &missing
, &n
, &def
);
3499 while (n
--) fprintf(stderr
, "sterm: missing fontset: %s\n", missing
[n
]);
3500 XFreeStringList(missing
);
3506 static void xgetfontinfo (XFontSet set
, int *ascent
, int *descent
, short *lbearing
, short *rbearing
, Font
*fid
) {
3507 XFontStruct
**xfonts
;
3511 *ascent
= *descent
= *lbearing
= *rbearing
= 0;
3512 n
= XFontsOfFontSet(set
, &xfonts
, &font_names
);
3513 for (int f
= 0; f
< n
; ++f
) {
3514 if (f
== 0) *fid
= (*xfonts
)->fid
;
3515 *ascent
= MAX(*ascent
, (*xfonts
)->ascent
);
3516 *descent
= MAX(*descent
, (*xfonts
)->descent
);
3517 *lbearing
= MAX(*lbearing
, (*xfonts
)->min_bounds
.lbearing
);
3518 *rbearing
= MAX(*rbearing
, (*xfonts
)->max_bounds
.rbearing
);
3524 static void initfonts (const char *fontstr
, const char *bfontstr
, const char *tabfont
) {
3525 if ((dc
.font
[0].set
= xinitfont(fontstr
)) == NULL
) {
3526 if ((dc
.font
[0].set
= xinitfont(FONT
)) == NULL
) die("can't load font %s", fontstr
);
3528 xgetfontinfo(dc
.font
[0].set
, &dc
.font
[0].ascent
, &dc
.font
[0].descent
, &dc
.font
[0].lbearing
, &dc
.font
[0].rbearing
, &dc
.font
[0].fid
);
3530 if ((dc
.font
[1].set
= xinitfont(bfontstr
)) == NULL
) {
3531 if ((dc
.font
[1].set
= xinitfont(FONTBOLD
)) == NULL
) die("can't load font %s", bfontstr
);
3533 xgetfontinfo(dc
.font
[1].set
, &dc
.font
[1].ascent
, &dc
.font
[1].descent
, &dc
.font
[1].lbearing
, &dc
.font
[1].rbearing
, &dc
.font
[1].fid
);
3535 if ((dc
.font
[2].set
= xinitfont(tabfont
)) == NULL
) {
3536 if ((dc
.font
[2].set
= xinitfont(FONTTAB
)) == NULL
) die("can't load font %s", tabfont
);
3538 xgetfontinfo(dc
.font
[2].set
, &dc
.font
[2].ascent
, &dc
.font
[2].descent
, &dc
.font
[2].lbearing
, &dc
.font
[2].rbearing
, &dc
.font
[2].fid
);
3542 static void xinit (void) {
3543 XSetWindowAttributes attrs
;
3545 XColor blackcolor
= { 0, 0, 0, 0, 0, 0 };
3547 if (!(xw
.dpy
= XOpenDisplay(NULL
))) die("can't open display");
3549 XA_VT_SELECTION
= XInternAtom(xw
.dpy
, "_STERM_SELECTION_", 0);
3550 XA_CLIPBOARD
= XInternAtom(xw
.dpy
, "CLIPBOARD", 0);
3551 XA_UTF8
= XInternAtom(xw
.dpy
, "UTF8_STRING", 0);
3552 XA_NETWM_NAME
= XInternAtom(xw
.dpy
, "_NET_WM_NAME", 0);
3553 XA_TARGETS
= XInternAtom(xw
.dpy
, "TARGETS", 0);
3554 xw
.xembed
= XInternAtom(xw
.dpy
, "_XEMBED", False
);
3556 xw
.scr
= XDefaultScreen(xw
.dpy
);
3558 initfonts(opt_fontnorm
, opt_fontbold
, opt_fonttab
);
3559 /* XXX: Assuming same size for bold font */
3560 xw
.cw
= dc
.font
[0].rbearing
-dc
.font
[0].lbearing
;
3561 xw
.ch
= dc
.font
[0].ascent
+dc
.font
[0].descent
;
3562 xw
.tch
= dc
.font
[2].ascent
+dc
.font
[2].descent
;
3563 xw
.tabheight
= opt_disabletabs
? 0 : xw
.tch
+2;
3564 //fprintf(stderr, "tabh: %d\n", xw.tabheight);
3567 xw
.cmap
= XDefaultColormap(xw
.dpy
, xw
.scr
);
3569 /* window - default size */
3570 term
->picbufh
= term
->row
*xw
.ch
;
3571 term
->picbufw
= term
->col
*xw
.cw
;
3573 xw
.h
= term
->picbufh
+xw
.tabheight
;
3574 xw
.w
= term
->picbufw
;
3576 attrs
.background_pixel
= getColor(defaultBG
);
3577 attrs
.border_pixel
= getColor(defaultBG
);
3578 attrs
.bit_gravity
= NorthWestGravity
;
3579 attrs
.event_mask
= FocusChangeMask
| KeyPressMask
3580 | ExposureMask
| VisibilityChangeMask
| StructureNotifyMask
3581 | /*ButtonMotionMask*/ PointerMotionMask
| ButtonPressMask
| ButtonReleaseMask
3582 | EnterWindowMask
| LeaveWindowMask
;
3583 attrs
.colormap
= xw
.cmap
;
3584 //fprintf(stderr, "oe: [%s]\n", opt_embed);
3585 parent
= opt_embed
? strtol(opt_embed
, NULL
, 0) : XRootWindow(xw
.dpy
, xw
.scr
);
3586 xw
.win
= XCreateWindow(xw
.dpy
, parent
, 0, 0,
3587 xw
.w
, xw
.h
, 0, XDefaultDepth(xw
.dpy
, xw
.scr
), InputOutput
,
3588 XDefaultVisual(xw
.dpy
, xw
.scr
),
3589 CWBackPixel
| CWBorderPixel
| CWBitGravity
| CWEventMask
3593 term
->picbuf
= XCreatePixmap(xw
.dpy
, xw
.win
, term
->picbufw
, term
->picbufh
, XDefaultDepth(xw
.dpy
, xw
.scr
));
3594 xw
.pictab
= XCreatePixmap(xw
.dpy
, xw
.win
, xw
.w
, xw
.tabheight
>0?xw
.tabheight
:1, XDefaultDepth(xw
.dpy
, xw
.scr
));
3596 if ((xw
.xim
= XOpenIM(xw
.dpy
, NULL
, NULL
, NULL
)) == NULL
) die("XOpenIM() failed");
3597 xw
.xic
= XCreateIC(xw
.xim
, XNInputStyle
, XIMPreeditNothing
| XIMStatusNothing
, XNClientWindow
, xw
.win
, XNFocusWindow
, xw
.win
, NULL
);
3599 dc
.gc
= XCreateGC(xw
.dpy
, xw
.win
, 0, NULL
);
3600 /* white cursor, black outline */
3601 xw
.cursor
= XCreateFontCursor(xw
.dpy
, XC_xterm
);
3602 XDefineCursor(xw
.dpy
, xw
.win
, xw
.cursor
);
3603 XRecolorCursor(xw
.dpy
, xw
.cursor
,
3604 &(XColor
){.red
= 0xffff, .green
= 0xffff, .blue
= 0xffff},
3605 &(XColor
){.red
= 0x0000, .green
= 0x0000, .blue
= 0x0000});
3606 fixWindowTitle(term
);
3607 //XStoreName(xw.dpy, xw.win, opt_title);
3609 XSetForeground(xw
.dpy
, dc
.gc
, 0);
3610 XFillRectangle(xw
.dpy
, term
->picbuf
, dc
.gc
, 0, 0, term
->picbufw
, term
->picbufh
);
3611 if (xw
.tabheight
> 0) XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, 0, 0, xw
.w
, xw
.tabheight
);
3613 XMapWindow(xw
.dpy
, xw
.win
);
3615 #if BLANKPTR_USE_GLYPH_CURSOR
3616 xw
.blankPtr
= XCreateGlyphCursor(xw
.dpy
, dc
.font
[0].fid
, dc
.font
[0].fid
, ' ', ' ', &blackcolor
, &blackcolor
);
3618 static const char cmbmp
[1] = {0};
3621 pm
= XCreateBitmapFromData(xw
.dpy
, xw
.win
, cmbmp
, 1, 1);
3622 xw
.blankPtr
= XCreatePixmapCursor(xw
.dpy
, pm
, pm
, &blackcolor
, &blackcolor
, 0, 0);
3623 XFreePixmap(xw
.dpy
, pm
);
3630 static void xblankPointer (void) {
3631 if (!ptrBlanked
&& xw
.blankPtr
!= None
) {
3633 XDefineCursor(xw
.dpy
, xw
.win
, xw
.blankPtr
);
3639 static void xunblankPointer (void) {
3640 if (ptrBlanked
&& xw
.cursor
!= None
) {
3642 XDefineCursor(xw
.dpy
, xw
.win
, xw
.cursor
);
3644 ptrLastMove
= mclock_ticks();
3649 static void xdraws (const char *s
, const Glyph
*base
, int x
, int y
, int charlen
, int bytelen
) {
3650 int fg
= base
->fg
, bg
= base
->bg
, temp
;
3651 int winx
= x
*xw
.cw
, winy
= y
*xw
.ch
+dc
.font
[0].ascent
, width
= charlen
*xw
.cw
;
3652 XFontSet fontset
= dc
.font
[0].set
;
3653 int defF
= base
->mode
&ATTR_DEFFG
, defB
= base
->mode
&ATTR_DEFBG
;
3655 /* only switch default fg/bg if term is in RV mode */
3656 if (IS_SET(MODE_REVERSE
)) {
3657 if (defF
) fg
= term
->defbg
;
3658 if (defB
) bg
= term
->deffg
;
3660 if (base
->mode
&ATTR_REVERSE
) defF
= defB
= 0;
3661 if (base
->mode
& ATTR_BOLD
) {
3662 if (defF
&& defB
&& defaultBoldFG
>= 0) fg
= defaultBoldFG
;
3663 else if (fg
< 8) fg
+= 8;
3664 fontset
= dc
.font
[1].set
;
3666 if (base
->mode
& ATTR_UNDERLINE
&& defaultUnderlineFG
>= 0) {
3667 if (defF
&& defB
) fg
= defaultUnderlineFG
;
3670 if (base
->mode
&ATTR_REVERSE
) { temp
= fg
; fg
= bg
; bg
= temp
; }
3672 XSetBackground(xw
.dpy
, dc
.gc
, getColor(bg
));
3673 XSetForeground(xw
.dpy
, dc
.gc
, getColor(fg
));
3677 FILE *fo = fopen("zlog.log", "ab");
3678 fprintf(fo, "xdraws: x=%d; y=%d; charlen=%d; bytelen=%d\n", x, y, charlen, bytelen);
3683 if (IS_GFX(base
->mode
)) {
3684 XmbDrawImageString(xw
.dpy
, term
->picbuf
, fontset
, dc
.gc
, winx
, winy
, s
, bytelen
);
3685 } else if (!needConversion
) {
3686 XmbDrawImageString(xw
.dpy
, term
->picbuf
, fontset
, dc
.gc
, winx
, winy
, s
, bytelen
);
3689 //k8: dunno why, but Xutf8DrawImageString() ignores ascii (at least for terminus)
3690 const char *pos
= s
;
3693 while (pos
< s
+bytelen
) {
3697 if ((unsigned char)(pos
[0]) < 128) {
3698 for (e
= pos
+1; e
< s
+bytelen
&& (unsigned char)(*e
) < 128; ++e
) ;
3700 XmbDrawImageString(xw
.dpy
, term
->picbuf
, fontset
, dc
.gc
, xpos
, winy
, pos
, e
-pos
);
3703 FILE *fo = fopen("zlog.log", "ab");
3704 fprintf(fo, " norm: clen=%d (%d); [", clen, (int)(e-pos));
3705 fwrite(pos, 1, e-pos, fo);
3711 for (clen
= 0, e
= pos
; e
< s
+bytelen
&& (unsigned char)(*e
) >= 128; ++e
) {
3712 if (((unsigned char)(e
[0])&0xc0) == 0xc0) ++clen
;
3714 Xutf8DrawImageString(xw
.dpy
, term
->picbuf
, fontset
, dc
.gc
, xpos
, winy
, pos
, e
-pos
);
3717 FILE *fo = fopen("zlog.log", "ab");
3718 fprintf(fo, " utf8: clen=%d (%d); [", clen, (int)(e-pos));
3719 fwrite(pos, 1, e-pos, fo);
3731 if (base
->mode
& ATTR_UNDERLINE
) {
3732 XDrawLine(xw
.dpy
, term
->picbuf
, dc
.gc
, winx
, winy
+1, winx
+width
-1, winy
+1);
3737 /* copy buffer pixmap to screen pixmap */
3738 static void xcopy (int x
, int y
, int cols
, int rows
) {
3739 int src_x
= x
*xw
.cw
, src_y
= y
*xw
.ch
, src_w
= cols
*xw
.cw
, src_h
= rows
*xw
.ch
;
3740 int dst_x
= src_x
, dst_y
= src_y
;
3742 if (opt_tabposition
== 1) { dst_y
+= xw
.tabheight
; }
3743 XCopyArea(xw
.dpy
, term
->picbuf
, xw
.win
, dc
.gc
, src_x
, src_y
, src_w
, src_h
, dst_x
, dst_y
);
3747 static void xdrawcursor (void) {
3749 int sl
, scrx
, scry
, cmy
;
3751 if (term
== NULL
) return;
3753 LIMIT(term
->oldcx
, 0, term
->col
-1);
3754 LIMIT(term
->oldcy
, 0, term
->row
-1);
3756 cmy
= term
->row
-term
->topline
-1;
3758 if (term
->cmdMode
== CMDMODE_NONE
|| term
->oldcy
!= cmy
) {
3760 scry
= term
->oldcy
+term
->topline
;
3761 if (scry
< term
->row
&&
3762 (term
->oldcy
!= term
->c
.y
|| term
->oldcx
!= term
->c
.x
|| (term
->c
.state
&CURSOR_HIDE
) || !(xw
.state
& WIN_FOCUSED
))) {
3763 /* remove the old cursor */
3764 sl
= utf8size(term
->line
[term
->oldcy
][scrx
].c
);
3765 g
= term
->line
[term
->oldcy
][scrx
];
3766 if (selected(scrx
, term
->c
.y
)) g
.mode
^= ATTR_REVERSE
;
3767 xdraws(g
.c
, &g
, scrx
, scry
, 1, sl
);
3768 //xclear(scrx, term->oldcy, scrx, term->oldcy);
3769 xcopy(scrx
, scry
, 1, 1);
3772 if (term
->cmdMode
!= CMDMODE_NONE
&& term
->oldcy
== cmy
) return;
3773 /* draw the new one */
3774 if (!(term
->c
.state
&CURSOR_HIDE
)) {
3776 scry
= term
->c
.y
+term
->topline
;
3777 if (scry
< term
->row
) {
3778 if (!(xw
.state
& WIN_FOCUSED
)) {
3779 if (defaultCursorInactiveBG
< 0) {
3780 XSetForeground(xw
.dpy
, dc
.gc
, getColor(defaultCursorBG
));
3781 XDrawRectangle(xw
.dpy
, term
->picbuf
, dc
.gc
, scrx
*xw
.cw
, scry
*xw
.ch
, xw
.cw
-1, xw
.ch
-1);
3784 g
.bg
= defaultCursorInactiveBG
;
3785 g
.fg
= defaultCursorInactiveFG
;
3787 g
.fg
= defaultCursorFG
;
3788 g
.bg
= defaultCursorBG
;
3790 memcpy(g
.c
, term
->line
[term
->c
.y
][scrx
].c
, UTF_SIZ
);
3793 if (IS_SET(MODE_REVERSE
)) g
.mode
|= ATTR_REVERSE
;
3795 xdraws(g
.c
, &g
, scrx
, scry
, 1, sl
);
3797 term
->oldcy
= term
->c
.y
;
3800 xcopy(scrx
, scry
, 1, 1);
3805 static void xdrawTabBar (void) {
3806 if (xw
.tabheight
> 0 && updateTabBar
) {
3807 static int tableft
= 0;
3809 int tabw
= xw
.w
/opt_tabcount
;
3810 XFontSet fontset
= dc
.font
[2].set
;
3812 XSetForeground(xw
.dpy
, dc
.gc
, getColor(normalTabBG
));
3813 XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, 0, 0, xw
.w
, xw
.tabheight
);
3815 if (termidx
< tableft
) tableft
= termidx
;
3816 else if (termidx
> tableft
+opt_tabcount
-1) tableft
= termidx
-opt_tabcount
+1;
3817 if (tableft
< 0) tableft
= 0;
3820 for (int f
= tabstart
; f
< tabstart
+opt_tabcount
; ++f
) {
3821 int x
= (f
-tabstart
)*tabw
;
3825 if (f
>= term_count
) {
3826 XSetForeground(xw
.dpy
, dc
.gc
, getColor(normalTabBG
));
3827 XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, x
, 0, xw
.w
, xw
.tabheight
);
3829 XSetForeground(xw
.dpy
, dc
.gc
, getColor(normalTabFG
));
3830 XDrawLine(xw
.dpy
, xw
.pictab
, dc
.gc
, x
/*+tabw-1*/, 0, x
/*+tabw-1*/, xw
.tabheight
);
3833 title
= term_array
[f
]->title
;
3834 if (!title
[0]) title
= opt_title
;
3835 tit
= SPrintf("[%d]%s", f
, title
);
3838 XSetForeground(xw
.dpy
, dc
.gc
, getColor(f
== termidx
? activeTabBG
: normalTabBG
));
3839 XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, x
, 0, tabw
, xw
.tabheight
);
3841 XSetBackground(xw
.dpy
, dc
.gc
, getColor(f
== termidx
? activeTabBG
: normalTabBG
));
3842 XSetForeground(xw
.dpy
, dc
.gc
, getColor(f
== termidx
? activeTabFG
: normalTabFG
));
3844 if (needConversion
) {
3847 while (*title
&& xx
< x
+tabw
) {
3848 const char *e
= title
;
3851 memset(&r
, 0, sizeof(r
));
3853 if ((unsigned char)(*e
) > 127) {
3854 while (*e
&& (unsigned char)(*e
) > 127) ++e
;
3855 Xutf8DrawImageString(xw
.dpy
, xw
.pictab
, fontset
, dc
.gc
, xx
, dc
.font
[2].ascent
+2, title
, e
-title
);
3856 Xutf8TextExtents(fontset
, title
, e
-title
, &r
, NULL
);
3858 while (*e
&& (unsigned char)(*e
) <= 127) ++e
;
3859 XmbDrawImageString(xw
.dpy
, xw
.pictab
, fontset
, dc
.gc
, xx
, dc
.font
[2].ascent
+2, title
, e
-title
);
3860 XmbTextExtents(fontset
, title
, e
-title
, &r
, NULL
);
3866 XmbDrawImageString(xw
.dpy
, xw
.pictab
, fontset
, dc
.gc
, x
+2, dc
.font
[2].ascent
+2, title
, strlen(title
));
3868 Xutf8DrawImageString(xw
.dpy
, xw
.pictab
, fontset
, dc
.gc
, x
+2, dc
.font
[2].ascent
+2, title
, strlen(title
));
3872 XSetForeground(xw
.dpy
, dc
.gc
, getColor(f
== termidx
? activeTabBG
: normalTabBG
));
3873 XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, x
+tabw
-2, 0, 2, xw
.tabheight
);
3876 XSetForeground(xw
.dpy
, dc
.gc
, getColor(normalTabFG
));
3877 XDrawLine(xw
.dpy
, xw
.pictab
, dc
.gc
, x
/*+tabw-1*/, 0, x
/*+tabw-1*/, xw
.tabheight
);
3881 XSetForeground(xw
.dpy
, dc
.gc
, getColor(normalTabFG
));
3882 //XDrawRectangle(xw.dpy, xw.pictab, dc.gc, -1, 0, xw.w+1, xw.tabheight+1);
3883 if (opt_tabposition
== 0) {
3884 XDrawLine(xw
.dpy
, xw
.pictab
, dc
.gc
, 0, 0, xw
.w
, 0);
3886 XDrawLine(xw
.dpy
, xw
.pictab
, dc
.gc
, 0, xw
.tabheight
-1, xw
.w
, xw
.tabheight
-1);
3889 if (opt_tabposition
== 0) {
3890 XCopyArea(xw
.dpy
, xw
.pictab
, xw
.win
, dc
.gc
, 0, 0, xw
.w
, xw
.tabheight
, 0, xw
.h
-xw
.tabheight
);
3892 XCopyArea(xw
.dpy
, xw
.pictab
, xw
.win
, dc
.gc
, 0, 0, xw
.w
, xw
.tabheight
, 0, 0);
3898 static void drawcmdline (int scry
) {
3900 int cpos
= term
->cmdofs
, bc
= 0, x
, sx
;
3901 int back
= (term
->cmdMode
== CMDMODE_INPUT
? 21 : 124);
3907 for (sx
= x
= 0; x
< term
->col
&& term
->cmdline
[cpos
]; ++x
) {
3908 int l
= utf8size(term
->cmdline
+cpos
);
3910 if (bc
+l
> DRAW_BUF_SIZ
) {
3911 xdraws(term
->drawbuf
, &base
, sx
, scry
, x
-sx
, bc
);
3915 memcpy(term
->drawbuf
+bc
, term
->cmdline
+cpos
, l
);
3919 if (bc
> 0) xdraws(term
->drawbuf
, &base
, sx
, scry
, x
-sx
, bc
);
3921 if (x
< term
->col
&& term
->cmdMode
== CMDMODE_INPUT
) {
3924 xdraws(" ", &base
, x
, scry
, 1, 1);
3928 if (x
< term
->col
) {
3931 memset(term
->drawbuf
, ' ', DRAW_BUF_SIZ
);
3932 while (x
< term
->col
) {
3935 if (x
> term
->col
) x
= term
->col
;
3936 xdraws(term
->drawbuf
, &base
, sx
, scry
, x
-sx
, x
-sx
);
3940 xcopy(0, scry
, term
->col
, 1);
3944 static void drawline (int x1
, int x2
, int scry
, int lineno
) {
3949 //dlogf("drawline: x1=%d; x2=%d; scry=%d; row:%d; lineno=%d\n", x1, x2, scry, term->row, lineno);
3950 if (scry
< 0 || scry
>= term
->row
) return;
3952 if (scry
== term
->row
-1 && term
->cmdMode
!= CMDMODE_NONE
) { drawcmdline(scry
); return; }
3954 if (lineno
< 0 || lineno
>= term
->linecount
) {
3955 xclear(0, scry
, term
->col
-1, scry
);
3956 xcopy(0, scry
, term
->col
, 1);
3958 if (lineno
< term
->row
&& term
->topline
== 0) {
3959 //if (term->topline != 0) term->dirty[lineno] = 2;
3960 if (!term
->dirty
[lineno
]) return;
3961 // fix 'dirty' flag for line
3962 if (term
->dirty
[lineno
]&0x02) {
3963 // mark full line as dirty
3964 for (int x
= 0; x
< term
->col
; ++x
) term
->line
[lineno
][x
].state
|= GLYPH_DIRTY
;
3966 // correct 'dirty' flag
3967 term
->dirty
[lineno
] = 0;
3968 if (x1
> 0) for (int x
= 0; x
< x1
; ++x
) if (term
->line
[lineno
][x
].state
&GLYPH_DIRTY
) { term
->dirty
[lineno
] = 1; break; }
3969 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; }
3971 // find dirty region
3972 for (stx
= x1
; stx
< x2
; ++stx
) if (term
->line
[lineno
][stx
].state
&GLYPH_DIRTY
) break;
3973 for (ex
= x2
; ex
> stx
; --ex
) if (term
->line
[lineno
][ex
-1].state
&GLYPH_DIRTY
) break;
3974 if (stx
>= x2
|| ex
<= stx
) return; // nothing to do
3975 //dlogf(" region: (%d,%d)\n", stx, ex);
3977 //if (lineno < term->row) term->dirty[lineno] = 0;
3982 base
= term
->line
[lineno
][stx
];
3985 //xclear(stx, scry, ex-1, scry); //k8: actually, we don't need this for good monospace fonts, so...
3986 for (int x
= stx
; x
< ex
; ++x
) {
3987 new = term
->line
[lineno
][x
];
3988 term
->line
[lineno
][x
].state
&= ~GLYPH_DIRTY
; //!
3989 if (term
->sel
.bx
!= -1 && new.c
[0]) {
3990 if ((lineno
< term
->row
&& selected(x
, lineno
)) || (lineno
>= term
->row
&& selected(x
, 0-(lineno
-term
->row
+1)))) new.mode
^= ATTR_REVERSE
;
3992 if (ib
> 0 && (ATTRCMP(base
, new) || ib
>= DRAW_BUF_SIZ
-UTF_SIZ
)) {
3993 // flush draw buffer
3994 xdraws(term
->drawbuf
, &base
, ox
, scry
, ic
, ib
);
3997 if (ib
== 0) { ox
= x
; base
= new; }
3998 sl
= utf8size(new.c
);
3999 memcpy(term
->drawbuf
+ib
, new.c
, sl
);
4003 if (ib
> 0) xdraws(term
->drawbuf
, &base
, ox
, scry
, ic
, ib
);
4004 //xcopy(0, scry, term->col, 1);
4005 if (term
->c
.y
== lineno
&& term
->c
.x
>= stx
&& term
->c
.x
< ex
) xdrawcursor();
4006 xcopy(stx
, scry
, ex
-stx
, 1);
4011 static void drawregion (int x1
, int y1
, int x2
, int y2
, int forced
) {
4012 if (!forced
&& (xw
.state
&WIN_VISIBLE
) == 0) {
4013 //dlogf("invisible");
4014 lastDrawTime
= term
->lastDrawTime
= 1;
4015 term
->wantRedraw
= 1;
4019 if (term
->topline
< term
->row
) {
4020 for (int y
= y1
; y
< y2
; ++y
) drawline(x1
, x2
, y
+term
->topline
, y
);
4022 if (term
->topline
> 0) {
4023 int scry
= MIN(term
->topline
, term
->row
), y
= term
->row
;
4025 if (term
->topline
>= term
->row
) y
+= term
->topline
-term
->row
;
4026 while (--scry
>= 0) {
4027 drawline(0, term
->col
, scry
, y
);
4034 lastDrawTime
= term
->lastDrawTime
= mclock_ticks();
4035 term
->wantRedraw
= 0;
4039 static void draw (int forced
) {
4041 //fprintf(stderr, "draw(%d) (%d)\n", forced, mclock_ticks());
4042 drawregion(0, 0, term
->col
, term
->row
, forced
);
4047 static void expose (XEvent
*ev
) {
4048 XExposeEvent
*e
= &ev
->xexpose
;
4050 if (xw
.state
&WIN_REDRAW
) {
4051 if (!e
->count
&& term
!= NULL
) {
4052 xw
.state
&= ~WIN_REDRAW
;
4053 xcopy(0, 0, term
->col
, term
->row
);
4055 } else if (term
!= NULL
) {
4056 //XCopyArea(xw.dpy, term->picbuf, xw.win, dc.gc, e->x, e->y, e->width, e->height, e->x, e->y+(opt_tabposition==1?xw.height:0)));
4057 xcopy(0, 0, term
->col
, term
->row
);
4064 static void visibility (XEvent
*ev
) {
4065 XVisibilityEvent
*e
= &ev
->xvisibility
;
4067 if (e
->state
== VisibilityFullyObscured
) xw
.state
&= ~WIN_VISIBLE
;
4068 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 */
4072 static void unmap (XEvent
*ev
) {
4073 xw
.state
&= ~WIN_VISIBLE
;
4077 static void xseturgency (int add
) {
4078 XWMHints
*h
= XGetWMHints(xw
.dpy
, xw
.win
);
4080 h
->flags
= add
? (h
->flags
| XUrgencyHint
) : (h
->flags
& ~XUrgencyHint
);
4081 XSetWMHints(xw
.dpy
, xw
.win
, h
);
4086 static void focus (XEvent
*ev
) {
4087 if (ev
->type
== FocusIn
) {
4088 xw
.state
|= WIN_FOCUSED
;
4092 xw
.state
&= ~WIN_FOCUSED
;
4098 xcopy(0, 0, term
->col
, term
->row
);
4102 ////////////////////////////////////////////////////////////////////////////////
4104 static const char *kmap (KeySym k
, uint state
) {
4105 const char *res
= NULL
;
4107 state
&= ~Mod2Mask
; // numlock
4108 for (int f
= 0; f
< keymap_used
; ++f
) {
4109 uint mask
= keymap
[f
].mask
;
4111 if (keymap
[f
].key
== k
&& ((mask
== XK_NO_MOD
&& !state
) || state
== mask
)) {
4112 //fprintf(stderr, "kp: %d\n", IS_SET(MODE_APPKEYPAD));
4113 if (!IS_SET(MODE_APPKEYPAD
)) {
4114 if (!keymap
[f
].kp
) {
4115 //fprintf(stderr, "nokp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4116 return keymap
[f
].str
; // non-keypad hit
4120 //fprintf(stderr, "kp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4121 if (keymap
[f
].kp
) return keymap
[f
].str
; // keypad hit
4122 res
= keymap
[f
].str
; // kp mode, but non-kp mapping found
4129 static const char *kbind (KeySym k
, uint state
) {
4130 state
&= ~Mod2Mask
; // numlock
4131 for (int f
= 0; f
< keybinds_used
; ++f
) {
4132 uint mask
= keybinds
[f
].mask
;
4134 if (keybinds
[f
].key
== k
&& ((mask
== XK_NO_MOD
&& !state
) || state
== mask
)) return keybinds
[f
].str
;
4140 static KeySym
do_keytrans (KeySym ks
) {
4141 for (int f
= 0; f
< keytrans_used
; ++f
) if (keytrans
[f
].src
== ks
) return keytrans
[f
].dst
;
4146 static void kpress (XEvent
*ev
) {
4147 XKeyEvent
*e
= &ev
->xkey
;
4148 KeySym ksym
= NoSymbol
;
4154 if (term
== NULL
) return;
4156 if (!ptrBlanked
&& opt_ptrblank
> 0) xblankPointer();
4158 //if (len < 1) len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
4159 if ((len
= Xutf8LookupString(xw
.xic
, e
, buf
, sizeof(buf
), &ksym
, &status
)) > 0) buf
[len
] = 0;
4160 // leave only known mods
4161 e
->state
&= (Mod1Mask
| Mod4Mask
| ControlMask
| ShiftMask
);
4164 const char *ksname
= XKeysymToString(ksym
);
4166 fprintf(stderr
, "utf(%d):[%s] (%s) 0x%08x\n", len
, len
>=0?buf
:"<shit>", ksname
, (unsigned int)e
->state
);
4169 if ((kstr
= kbind(ksym
, e
->state
)) != NULL
) {
4171 executeCommands(kstr
);
4175 if (term
->cmdMode
!= CMDMODE_NONE
) {
4176 int mode
= term
->cmdMode
;
4178 switch (do_keytrans(ksym
)) {
4181 if (mode
== CMDMODE_INPUT
) executeCommands(term
->cmdline
);
4184 if (mode
== CMDMODE_INPUT
) utf8choplast(term
->cmdline
);
4191 if (mode
== CMDMODE_INPUT
) {
4192 if (len
> 0 && (unsigned char)buf
[0] >= 32) tcmdput(buf
, len
);
4199 if ((kstr
= kmap(do_keytrans(ksym
), e
->state
)) != NULL
) {
4205 int meta
= (e
->state
&Mod1Mask
);
4207 int shift = (e->state&ShiftMask);
4208 int ctrl = (e->state&ControlMask);
4214 ttywritestr("\x1b\x0a");
4216 if (IS_SET(MODE_CRLF
)) ttywritestr("\r\n"); else ttywritestr("\r");
4222 if (meta
&& len
== 1) ttywritestr("\x1b");
4231 ////////////////////////////////////////////////////////////////////////////////
4233 static void cmessage (XEvent
*e
) {
4234 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
4235 if (e
->xclient
.message_type
== xw
.xembed
&& e
->xclient
.format
== 32) {
4236 if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_IN
) {
4237 xw
.state
|= WIN_FOCUSED
;
4240 } else if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_OUT
) {
4241 xw
.state
&= ~WIN_FOCUSED
;
4246 xcopy(0, 0, term
->col
, term
->row
);
4251 ////////////////////////////////////////////////////////////////////////////////
4252 static void resize (XEvent
*e
) {
4256 //if (e->xconfigure.width == 65535 || e->xconfigure.width == -1) e->xconfigure.width = xw.w;
4257 if (e
->xconfigure
.height
== 65535 || e
->xconfigure
.height
== -1) e
->xconfigure
.height
= xw
.h
;
4258 //if ((short int)e->xconfigure.height < xw.ch) return;
4260 if (e
->xconfigure
.width
== xw
.w
&& e
->xconfigure
.height
== xw
.h
) return;
4261 xw
.w
= e
->xconfigure
.width
;
4262 xw
.h
= e
->xconfigure
.height
;
4264 row
= (xw
.h
-xw
.tabheight
)/xw
.ch
;
4265 //fprintf(stderr, "neww=%d; newh=%d; ch=%d; th=%d; col=%d; row=%d; ocol=%d; orow=%d\n", xw.w, xw.h, xw.ch, xw.tabheight, col, row, term->col, term->row);
4266 if (col
== term
->col
&& row
== term
->row
) return;
4267 for (int f
= 0; f
< term_count
; ++f
) {
4268 term
= term_array
[f
];
4269 if (tresize(col
, row
) && ot
== term
) draw(1);
4274 XFreePixmap(xw
.dpy
, xw
.pictab
);
4275 xw
.pictab
= XCreatePixmap(xw
.dpy
, xw
.win
, xw
.w
, xw
.tabheight
>0?xw
.tabheight
:1, XDefaultDepth(xw
.dpy
, xw
.scr
));
4280 static inline int last_draw_too_old (void) {
4281 int tt
= mclock_ticks();
4284 if (term
->dead
|| !term
->wantRedraw
) return 0;
4285 if (tt
-term
->lastDrawTime
>= opt_drawtimeout
) return 1;
4288 return (tt
-lastDrawTime
>= 500);
4292 ////////////////////////////////////////////////////////////////////////////////
4294 static void (*handler
[LASTEvent
])(XEvent
*) = {
4295 [KeyPress
] = kpress
,
4296 [ClientMessage
] = cmessage
,
4297 [ConfigureNotify
] = resize
,
4298 [VisibilityNotify
] = visibility
,
4299 [UnmapNotify
] = unmap
,
4303 [MotionNotify
] = bmotion
,
4304 [ButtonPress
] = bpress
,
4305 [ButtonRelease
] = brelease
,
4306 [SelectionNotify
] = selnotify
,
4307 [SelectionRequest
] = selrequest
,
4308 [SelectionClear
] = selclear
,
4312 static void run (void) {
4313 //int stuff_to_print = 0;
4314 int xfd
= XConnectionNumber(xw
.dpy
);
4316 ptrLastMove
= mclock_ticks();
4317 while (term_count
> 0) {
4320 struct timeval timeout
;
4327 //FD_SET(term->cmdfd, &rfd);
4328 // have something to write?
4329 for (int f
= 0; f
< term_count
; ++f
) {
4330 Term
*t
= term_array
[f
];
4332 if (!t
->dead
&& term
->cmdfd
>= 0 && t
->pid
!= 0) {
4333 if (t
->cmdfd
> maxfd
) maxfd
= t
->cmdfd
;
4334 FD_SET(t
->cmdfd
, &rfd
);
4335 if (t
->wrbufpos
< t
->wrbufused
) FD_SET(t
->cmdfd
, &wfd
);
4340 timeout
.tv_usec
= (opt_drawtimeout
+2)*1000;
4341 if (select(maxfd
+1, &rfd
, &wfd
, NULL
, &timeout
) < 0) {
4342 if (errno
== EINTR
) continue;
4343 die("select failed: %s", SERRNO
);
4347 for (int f
= 0; f
< term_count
; ++f
) {
4348 Term
*t
= term_array
[f
];
4350 if (!t
->dead
&& term
->cmdfd
>= 0 && term
->pid
!= 0) {
4352 if (FD_ISSET(t
->cmdfd
, &wfd
)) ttyflushwrbuf();
4353 if (FD_ISSET(t
->cmdfd
, &rfd
)) ttyread(); //t->wantRedraw = 1;
4359 if (term_count
== 0) exit(exitcode
);
4361 if (updateTabBar
|| last_draw_too_old()) draw(0);
4363 if (XPending(xw
.dpy
)) {
4364 while (XPending(xw
.dpy
)) {
4365 XNextEvent(xw
.dpy
, &ev
);
4367 //case VisibilityNotify:
4375 ptrLastMove
= mclock_ticks();
4379 if (XFilterEvent(&ev
, xw
.win
)) continue;
4380 if (handler
[ev
.type
]) (handler
[ev
.type
])(&ev
);
4384 if (!ptrBlanked
&& opt_ptrblank
> 0 && mclock_ticks()-ptrLastMove
>= opt_ptrblank
) {
4391 ////////////////////////////////////////////////////////////////////////////////
4392 typedef const char * (*IniHandlerFn
) (const char *optname
, const char *fmt
, char *argstr
, void *udata
);
4402 static const char *inifnGenericOneArg (const char *optname
, const char *fmt
, char *argstr
, void *udata
) {
4403 return iniParseArguments(argstr
, fmt
, udata
);
4407 static const char *inifnGenericOneStr (const char *optname
, const char *fmt
, char *argstr
, void *udata
) {
4409 const char *err
= iniParseArguments(argstr
, fmt
, &s
);
4411 if (err
!= NULL
) return err
;
4412 if ((s
= strdup(s
)) == NULL
) return "out of memory";
4414 char **ustr
= (char **)udata
;
4416 if (*ustr
) free(*ustr
);
4423 static const IniCommand iniCommands
[] = {
4424 {"term", "s!-", &opt_term
, inifnGenericOneStr
},
4425 {"class", "s!-", &opt_class
, inifnGenericOneStr
},
4426 {"title", "s!-", &opt_title
, inifnGenericOneStr
},
4427 {"fontnorm", "s!-", &opt_fontnorm
, inifnGenericOneStr
},
4428 {"fontbold", "s!-", &opt_fontbold
, inifnGenericOneStr
},
4429 {"fonttab", "s!-", &opt_fonttab
, inifnGenericOneStr
},
4430 {"shell", "s!-", &opt_shell
, inifnGenericOneStr
},
4431 {"doubleclick_timeout", "i{0,10000}", &opt_doubleclick_timeout
, inifnGenericOneArg
},
4432 {"tripleclick_timeout", "i{0,10000}", &opt_tripleclick_timeout
, inifnGenericOneArg
},
4433 {"tabsize", "i{1,256}", &opt_tabsize
, inifnGenericOneArg
},
4434 {"defaultfg", "i{0,511}", &defaultFG
, inifnGenericOneArg
},
4435 {"defaultbg", "i{0,511}", &defaultBG
, inifnGenericOneArg
},
4436 {"defaultcursorfg", "i{0,511}", &defaultCursorFG
, inifnGenericOneArg
},
4437 {"defaultcursorbg", "i{0,511}", &defaultCursorBG
, inifnGenericOneArg
},
4438 {"defaultinactivecursorfg", "i{0,511}", &defaultCursorInactiveFG
, inifnGenericOneArg
},
4439 {"defaultinactivecursorbg", "i{-1,511}", &defaultCursorInactiveBG
, inifnGenericOneArg
},
4440 {"defaultboldfg", "i{-1,511}", &defaultBoldFG
, inifnGenericOneArg
},
4441 {"defaultunderlinefg", "i{-1,511}", &defaultUnderlineFG
, inifnGenericOneArg
},
4442 {"normaltabfg", "i{0,511}", &normalTabFG
, inifnGenericOneArg
},
4443 {"normaltabbg", "i{0,511}", &normalTabBG
, inifnGenericOneArg
},
4444 {"activetabfg", "i{0,511}", &activeTabFG
, inifnGenericOneArg
},
4445 {"activetabbg", "i{0,511}", &activeTabBG
, inifnGenericOneArg
},
4446 {"maxhistory", "i{0,65535}", &opt_maxhistory
, inifnGenericOneArg
},
4447 {"ptrblank", "i{0,65535}", &opt_ptrblank
, inifnGenericOneArg
},
4448 {"tabcount", "i{1,128}", &opt_tabcount
, inifnGenericOneArg
},
4449 {"tabposition", "i{0,1}", &opt_tabposition
, inifnGenericOneArg
},
4450 {"draw_timeout", "i{5,30000}", &opt_drawtimeout
, inifnGenericOneArg
},
4451 {"audiblebell", "b", &opt_audiblebell
, inifnGenericOneArg
},
4452 {NULL
, NULL
, NULL
, NULL
}
4456 #define MISC_CMD_NONE ((const char *)-1)
4459 // NULL: command processed; MISC_CMD_NONE: unknown command; !NULL: error
4460 static const char *processMiscCmds (const char *optname
, char *argstr
) {
4461 const char *err
= NULL
;
4463 if (strcasecmp(optname
, "unimap") == 0) {
4467 //unimap 0x2592 0x61 alt
4468 if ((err
= iniParseArguments(argstr
, "i{0,65535}i{0,255}|s!-", &uni
, &ch
, &alt
)) != NULL
) return err
;
4469 if (alt
!= NULL
&& strcasecmp(alt
, "alt") != 0) return "invalid unimap";
4470 if (unimap
== NULL
) {
4471 if ((unimap
= calloc(65536, sizeof(unimap
[0]))) == NULL
) return "out of memory";
4473 if (alt
!= NULL
&& ch
== 0) alt
= NULL
;
4474 if (alt
!= NULL
&& ch
< 96) return "invalid unimap";
4476 if (alt
!= NULL
) unimap
[uni
] |= 0x8000;
4480 if (strcasecmp(optname
, "keytrans_reset") == 0) {
4481 if ((err
= iniParseArguments(argstr
, "")) != NULL
) return err
;
4485 if (strcasecmp(optname
, "keytrans") == 0) {
4486 char *src
= NULL
, *dst
= NULL
;
4488 if (iniParseArguments(argstr
, "R!-", &argstr
) == NULL
) {
4489 if (strcasecmp(argstr
, "reset") == 0 || strcasecmp(argstr
, "clear") == 0) {
4494 if ((err
= iniParseArguments(argstr
, "s!-s!-", &src
, &dst
)) != NULL
) return err
;
4495 keytrans_add(src
, dst
);
4499 if (strcasecmp(optname
, "keybind_reset") == 0) {
4500 if ((err
= iniParseArguments(argstr
, "")) != NULL
) return err
;
4504 if (strcasecmp(optname
, "keybind") == 0) {
4505 char *key
= NULL
, *act
= NULL
;
4506 if (iniParseArguments(argstr
, "R!-", &argstr
) == NULL
) {
4507 if (strcasecmp(argstr
, "reset") == 0 || strcasecmp(argstr
, "clear") == 0) {
4512 if ((err
= iniParseArguments(argstr
, "s!-R!", &key
, &act
)) != NULL
) return err
;
4513 keybind_add(key
, act
);
4517 if (strcasecmp(optname
, "keymap_reset") == 0) {
4518 if ((err
= iniParseArguments(argstr
, "")) != NULL
) return err
;
4522 if (strcasecmp(optname
, "keymap") == 0) {
4523 char *key
= NULL
, *str
= NULL
;
4525 if (iniParseArguments(argstr
, "R!-", &argstr
) == NULL
) {
4526 if (strcasecmp(argstr
, "reset") == 0 || strcasecmp(argstr
, "clear") == 0) {
4531 if ((err
= iniParseArguments(argstr
, "s!-s!-", &key
, &str
)) != NULL
) return err
;
4532 keymap_add(key
, str
);
4536 return MISC_CMD_NONE
;
4540 #define INI_LINE_SIZE (32768)
4542 // <0: file not found
4543 // >0: file loading error
4545 static int loadConfig (const char *fname
) {
4546 int inifelse
= 0; // 0: not; 1: doing true; 2: doing false; -1: waiting else/endif; -2: waiting endif
4547 FILE *fi
= fopen(fname
, "r");
4548 const char *err
= NULL
;
4552 if (fi
== NULL
) return -1;
4553 if ((line
= malloc(INI_LINE_SIZE
)) == NULL
) { err
= "out of memory"; goto quit
; }
4555 while (fgets(line
, INI_LINE_SIZE
-1, fi
) != NULL
) {
4556 char *optname
, *argstr
;
4560 line
[INI_LINE_SIZE
-1] = 0;
4562 for (optname
= line
; *optname
&& isspace(*optname
); ++optname
) ;
4563 if (!optname
[0] || optname
[0] == '#') continue; // comment
4564 if (!isalnum(optname
[0])) { err
= "invalid option name"; goto quit
; }
4567 if (!argstr
[0] || isspace(argstr
[0])) break;
4568 if (!isalnum(argstr
[0]) && argstr
[0] != '_' && argstr
[0] != '.') { err
= "invalid option name"; goto quit
; }
4569 *argstr
= tolower(*argstr
);
4572 if (*argstr
) *argstr
++ = 0;
4574 if (strcasecmp(optname
, "ifterm") == 0) {
4577 if (inifelse
!= 0) { err
= "nested ifs are not allowed"; goto quit
; }
4579 if ((err
= iniParseArguments(argstr
, "s", &val
)) != NULL
) goto quit
;
4580 if (strcasecmp(opt_term
, val
) == 0) inifelse
= 1;
4583 if (strcasecmp(optname
, "else") == 0) {
4585 case -1: inifelse
= 2; break;
4586 case 2: case -2: case 0: err
= "else without if"; goto quit
;
4587 case 1: inifelse
= -2; break;
4591 if (strcasecmp(optname
, "endif") == 0) {
4593 case -1: case -2: case 1: case 2: inifelse
= 0; break;
4594 case 0: err
= "endif without if"; goto quit
;
4601 //fprintf(stderr, "skip: [%s]\n", argstr);
4604 if (opt_term_locked
&& strcasecmp(optname
, "term") == 0) continue; // termname given in command line
4605 // ok, we have option name in `optname` and arguments in `argstr`
4606 if (strncmp(optname
, "color.", 6) == 0) {
4611 if (!optname
[0]) { err
= "invalid color option"; goto quit
; }
4613 if (!isdigit(*optname
)) { err
= "invalid color option"; goto quit
; }
4614 n
= (n
*10)+(optname
[0]-'0');
4617 if (n
< 0 || n
> 511) { err
= "invalid color index"; goto quit
; }
4619 if ((err
= iniParseArguments(argstr
, "s!-", &s
)) != NULL
) goto quit
;
4620 if ((s
= strdup(s
)) == NULL
) { err
= "out of memory"; goto quit
; }
4621 if (opt_colornames
[n
] != NULL
) free(opt_colornames
[n
]);
4622 opt_colornames
[n
] = s
;
4626 if ((err
= processMiscCmds(optname
, argstr
)) != MISC_CMD_NONE
) {
4627 if (err
!= NULL
) goto quit
;
4633 for (int f
= 0; iniCommands
[f
].name
!= NULL
; ++f
) {
4634 if (strcmp(iniCommands
[f
].name
, optname
) == 0) {
4635 if ((err
= iniCommands
[f
].fn(optname
, iniCommands
[f
].fmt
, argstr
, iniCommands
[f
].udata
)) != NULL
) goto quit
;
4641 fprintf(stderr
, "ini error at line %d: unknown option '%s'!\n", lineno
, optname
);
4645 if (line
!= NULL
) free(line
);
4647 if (err
== NULL
&& inifelse
!= 0) err
= "if without endif";
4648 if (err
!= NULL
) die("ini error at line %d: %s", lineno
, err
);
4653 static void initDefaultOptions (void) {
4654 opt_title
= strdup("sterm");
4655 opt_class
= strdup("sterm");
4656 opt_term
= strdup(TNAME
);
4657 opt_fontnorm
= strdup(FONT
);
4658 opt_fontbold
= strdup(FONTBOLD
);
4659 opt_fonttab
= strdup(FONTTAB
);
4660 opt_shell
= strdup(SHELL
);
4662 memset(opt_colornames
, 0, sizeof(opt_colornames
));
4663 for (int f
= 0; f
< LEN(defcolornames
); ++f
) opt_colornames
[f
] = strdup(defcolornames
[f
]);
4664 for (int f
= 0; f
< LEN(defextcolornames
); ++f
) opt_colornames
[f
+256] = strdup(defextcolornames
[f
]);
4666 keytrans_add("KP_Home", "Home");
4667 keytrans_add("KP_Left", "Left");
4668 keytrans_add("KP_Up", "Up");
4669 keytrans_add("KP_Right", "Right");
4670 keytrans_add("KP_Down", "Down");
4671 keytrans_add("KP_Prior", "Prior");
4672 keytrans_add("KP_Next", "Next");
4673 keytrans_add("KP_End", "End");
4674 keytrans_add("KP_Begin", "Begin");
4675 keytrans_add("KP_Insert", "Insert");
4676 keytrans_add("KP_Delete", "Delete");
4678 keybind_add("shift+Insert", "PastePrimary");
4679 keybind_add("alt+Insert", "PasteSecondary");
4681 keymap_add("BackSpace", "\177");
4682 keymap_add("Insert", "\x1b[2~");
4683 keymap_add("Delete", "\x1b[3~");
4684 keymap_add("Home", "\x1b[1~");
4685 keymap_add("End", "\x1b[4~");
4686 keymap_add("Prior", "\x1b[5~");
4687 keymap_add("Next", "\x1b[6~");
4688 keymap_add("F1", "\x1bOP");
4689 keymap_add("F2", "\x1bOQ");
4690 keymap_add("F3", "\x1bOR");
4691 keymap_add("F4", "\x1bOS");
4692 keymap_add("F5", "\x1b[15~");
4693 keymap_add("F6", "\x1b[17~");
4694 keymap_add("F7", "\x1b[18~");
4695 keymap_add("F8", "\x1b[19~");
4696 keymap_add("F9", "\x1b[20~");
4697 keymap_add("F10", "\x1b[21~");
4698 keymap_add("Up", "\x1bOA");
4699 keymap_add("Down", "\x1bOB");
4700 keymap_add("Right", "\x1bOC");
4701 keymap_add("Left", "\x1bOD");
4702 keymap_add("kpad+Up", "\x1bOA");
4703 keymap_add("kpad+Down", "\x1bOB");
4704 keymap_add("kpad+Right", "\x1bOC");
4705 keymap_add("kpad+Left", "\x1bOD");
4709 ////////////////////////////////////////////////////////////////////////////////
4710 static Term
*oldTerm
;
4711 static int oldTermIdx
;
4712 static Term
*newTerm
;
4713 static int newTermIdx
;
4714 static int newTermSwitch
;
4717 typedef void (*CmdHandlerFn
) (const char *cmdname
, char *argstr
);
4725 static void cmdPastePrimary (const char *cmdname
, char *argstr
) {
4726 selpaste(XA_PRIMARY
);
4730 static void cmdPasteSecondary (const char *cmdname
, char *argstr
) {
4731 selpaste(XA_SECONDARY
);
4735 static void cmdPasteClipboard (const char *cmdname
, char *argstr
) {
4736 selpaste(XA_CLIPBOARD
);
4740 static void cmdExec (const char *cmdname
, char *argstr
) {
4741 if (term
->execcmd
!= NULL
) free(term
->execcmd
);
4743 term
->execcmd
= strdup(argstr
);
4745 term
->execcmd
= NULL
;
4750 static int parseTabArgs (char *argstr
, int *noswitch
, int nowrap
, int idx
) {
4754 while (*argstr
&& isspace(*argstr
)) ++argstr
;
4755 if (!argstr
[0]) break;
4756 if (iniParseArguments(argstr
, "s-R-", &arg
, &argstr
) != NULL
) break;
4758 if (strcasecmp(arg
, "noswitch") == 0) *noswitch
= 1;
4759 else if (strcasecmp(arg
, "switch") == 0) *noswitch
= 0;
4760 else if (strcasecmp(arg
, "nowrap") == 0) nowrap
= 1;
4761 else if (strcasecmp(arg
, "wrap") == 0) nowrap
= 0;
4762 else if (strcasecmp(arg
, "first") == 0) idx
= 0;
4763 else if (strcasecmp(arg
, "last") == 0) idx
= term_count
-1;
4764 else if (strcasecmp(arg
, "prev") == 0) idx
= -1;
4765 else if (strcasecmp(arg
, "next") == 0) idx
= -2;
4770 n
= strtol(arg
, &eptr
, 0);
4771 if (!eptr
[0] && n
>= 0 && n
< term_count
) idx
= n
;
4776 if ((idx
= termidx
-1) < 0) idx
= nowrap
? 0 : term_count
-1;
4779 if ((idx
= termidx
+1) >= term_count
) idx
= nowrap
? term_count
-1 : 0;
4786 static void flushNewTerm (void) {
4787 if (newTerm
!= NULL
) {
4788 if (newTermSwitch
&& term
!= NULL
) term
->lastActiveTime
= mclock_ticks();
4790 termidx
= newTermIdx
;
4791 tinitialize(term_array
[0]->col
, term_array
[0]->row
);
4793 term
->picbufh
= term
->row
*xw
.ch
;
4794 term
->picbufw
= term
->col
*xw
.cw
;
4795 term
->picbuf
= XCreatePixmap(xw
.dpy
, xw
.win
, term
->picbufw
, term
->picbufh
, XDefaultDepth(xw
.dpy
, xw
.scr
));
4796 XFillRectangle(xw
.dpy
, term
->picbuf
, dc
.gc
, 0, 0, term
->picbufw
, term
->picbufh
);
4798 if (ttynew(term
) != 0) {
4800 termidx
= oldTermIdx
;
4801 termfree(newTermIdx
);
4805 if (newTermSwitch
) {
4808 switchToTerm(newTermIdx
, 1);
4810 oldTermIdx
= termidx
;
4813 termidx
= oldTermIdx
;
4821 static void cmdNewTab (const char *cmdname
, char *argstr
) {
4822 int noswitch
= 0, idx
;
4824 if (opt_disabletabs
) return;
4826 if ((newTerm
= termalloc()) == NULL
) return;
4827 /*idx =*/ parseTabArgs(argstr
, &noswitch
, 0, termidx
);
4830 if (term
!= NULL
) term
->lastActiveTime
= mclock_ticks();
4833 newTermIdx
= termidx
= idx
;
4835 newTermSwitch
= !noswitch
;
4839 static void cmdCloseTab (const char *cmdname
, char *argstr
) {
4841 if (!term
->dead
) kill(term
->pid
, SIGTERM
);
4845 static void cmdKillTab (const char *cmdname
, char *argstr
) {
4847 if (!term
->dead
) kill(term
->pid
, SIGKILL
);
4851 static void cmdSwitchToTab (const char *cmdname
, char *argstr
) {
4852 int noswitch
= 0, idx
;
4855 idx
= parseTabArgs(argstr
, &noswitch
, 0, -666);
4857 switchToTerm(idx
, 1);
4859 oldTermIdx
= termidx
;
4864 static void cmdMoveTabTo (const char *cmdname
, char *argstr
) {
4865 int noswitch
= 0, idx
;
4868 idx
= parseTabArgs(argstr
, &noswitch
, 0, termidx
);
4869 if (idx
!= termidx
&& idx
>= 0 && idx
< term_count
) {
4870 Term
*t
= term_array
[termidx
];
4872 // remove current term
4873 for (int f
= termidx
+1; f
< term_count
; ++f
) term_array
[f
-1] = term_array
[f
];
4875 for (int f
= term_count
-2; f
>= idx
; --f
) term_array
[f
+1] = term_array
[f
];
4876 term_array
[idx
] = t
;
4885 static void cmdDefaultFG (const char *cmdname
, char *argstr
) {
4889 if (iniParseArguments(argstr
, "i{0,511}|s-", &c
, &s
) == NULL
) {
4890 if (s
!= NULL
&& tolower(s
[0]) == 'g') defaultFG
= c
; else term
->deffg
= c
;
4895 static void cmdDefaultBG (const char *cmdname
, char *argstr
) {
4899 if (iniParseArguments(argstr
, "i{0,511}|s-", &c
) == NULL
) {
4900 if (s
!= NULL
&& tolower(s
[0]) == 'g') defaultBG
= c
; else term
->defbg
= c
;
4905 static void scrollHistory (int delta
) {
4906 if (term
->maxhistory
< 1) return; // no history
4907 term
->topline
+= delta
;
4908 if (term
->topline
> term
->maxhistory
-term
->row
) term
->topline
= term
->maxhistory
-term
->row
;
4909 if (term
->topline
< 0) term
->topline
= 0;
4915 static void cmdScrollHistoryLineUp (const char *cmdname
, char *argstr
) {
4920 static void cmdScrollHistoryPageUp (const char *cmdname
, char *argstr
) {
4921 scrollHistory(term
->row
);
4925 static void cmdScrollHistoryLineDown (const char *cmdname
, char *argstr
) {
4930 static void cmdScrollHistoryPageDown (const char *cmdname
, char *argstr
) {
4931 scrollHistory(-term
->row
);
4935 static void cmdScrollHistoryTop (const char *cmdname
, char *argstr
) {
4936 scrollHistory(term
->linecount
);
4940 static void cmdScrollHistoryBottom (const char *cmdname
, char *argstr
) {
4941 scrollHistory(-term
->linecount
);
4945 static void cmdUTF8Locale (const char *cmdname
, char *argstr
) {
4948 if (iniParseArguments(argstr
, "b", &b
) == NULL
) {
4949 if (!needConversion
) b
= 1;
4950 if (term
!= NULL
) term
->needConv
= !b
;
4951 //fprintf(stderr, "needConv: %d (%d)\n", term->needConv, needConversion);
4956 static void cmdAudibleBell (const char *cmdname
, char *argstr
) {
4957 if (term
== NULL
) return;
4960 int toggle
= 0, *iptr
= &term
->audiblebell
;
4965 if (iniParseArguments(argstr
, "R-", &argstr
) != NULL
) break;
4966 if (!argstr
[0]) break;
4968 if (iniParseArguments(argstr
, "s!-R-", &s
, &argstr
) != NULL
) break;
4969 if (strcasecmp(s
, "toggle") == 0) toggle
= 1;
4970 else if (tolower(s
[0]) == 'g') iptr
= &opt_audiblebell
;
4972 if (iniParseArguments(s
, "b", &b
) != NULL
) return;
4977 tcmdlinemsg(*iptr
? "AudibleBell: 1" : "AudibleBell: 0");
4979 if (toggle
) *iptr
= !(*iptr
); else *iptr
= b
;
4984 static void cmdCommandMode (const char *cmdname
, char *argstr
) {
4986 if (term
->cmdMode
== CMDMODE_NONE
) tcmdlineinit();
4992 static void cmdCursor (const char *cmdname
, char *argstr
) {
4996 if (iniParseArguments(argstr
, "s!-", &s
) != NULL
) {
4997 tcmdlinemsg((term
->c
.state
&CURSOR_HIDE
) ? "cursor is hidden" : "cursor is visible");
4999 if (strcasecmp(s
, "show") == 0) term
->c
.state
&= ~CURSOR_HIDE
;
5000 else if (strcasecmp(s
, "hide") == 0) term
->c
.state
|= CURSOR_HIDE
;
5001 term
->wantRedraw
= 1;
5007 static void cmdResetAttrs (const char *cmdname
, char *argstr
) {
5009 term
->c
.attr
.mode
&= ~(ATTR_REVERSE
| ATTR_UNDERLINE
| ATTR_BOLD
);
5010 term
->c
.attr
.mode
|= ATTR_DEFFG
| ATTR_DEFBG
;
5011 term
->c
.attr
.fg
= term
->deffg
;
5012 term
->c
.attr
.bg
= term
->defbg
;
5017 static void cmdResetCharset (const char *cmdname
, char *argstr
) {
5019 term
->c
.attr
.mode
&= ~(ATTR_GFX
|ATTR_GFX1
);
5025 static void cmdScreen (const char *cmdname
, char *argstr
) {
5029 if (iniParseArguments(argstr
, "s!-", &s
) != NULL
) {
5030 tcmdlinemsg(IS_SET(MODE_ALTSCREEN
) ? "screen: alt" : "screen: norm");
5032 if (strcasecmp(s
, "norm") == 0) {
5033 if (IS_SET(MODE_ALTSCREEN
)) tswapscreen();
5034 } else if (strcasecmp(s
, "alt") == 0) {
5035 if (!IS_SET(MODE_ALTSCREEN
)) tswapscreen();
5043 static void cmdMouseReports (const char *cmdname
, char *argstr
) {
5047 if (iniParseArguments(argstr
, "b", &b
) != NULL
) {
5048 tcmdlinemsg(IS_SET(MODE_MOUSE
) ? "mouse reports are on" : "mouse reports are off");
5050 if (b
) term
->mode
|= MODE_MOUSEBTN
; else term
->mode
&= ~MODE_MOUSEBTN
;
5056 static void cmdMonochrome (const char *cmdname
, char *argstr
) {
5057 int b
= -1, global
= 0;
5059 if (term
== NULL
) return;
5064 if (iniParseArguments(argstr
, "R-", &argstr
) != NULL
) break;
5065 if (!argstr
[0]) break;
5067 if (iniParseArguments(argstr
, "s!-R-", &s
, &argstr
) != NULL
) break;
5068 if (tolower(s
[0]) == 'g') global
= 1;
5070 if (iniParseArguments(s
, "i{0,2}", &b
) != NULL
) return;
5077 b
= (global
? globalBW
: term
->blackandwhite
);
5078 sprintf(buf
, "Monochrome: %d", b
);
5081 if (global
) globalBW
= b
; else term
->blackandwhite
= b
;
5087 static const Command commandList
[] = {
5088 {"PastePrimary", cmdPastePrimary
},
5089 {"PasteSecondary", cmdPasteSecondary
},
5090 {"PasteClipboard", cmdPasteClipboard
},
5092 {"NewTab", cmdNewTab
}, // 'noswitch' 'next' 'prev' 'first' 'last'
5093 {"CloseTab", cmdCloseTab
},
5094 {"KillTab", cmdKillTab
},
5095 {"SwitchToTab", cmdSwitchToTab
}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5096 {"MoveTabTo", cmdMoveTabTo
}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5097 {"defaultfg", cmdDefaultFG
},
5098 {"defaultbg", cmdDefaultBG
},
5099 {"ScrollHistoryLineUp", cmdScrollHistoryLineUp
},
5100 {"ScrollHistoryPageUp", cmdScrollHistoryPageUp
},
5101 {"ScrollHistoryLineDown", cmdScrollHistoryLineDown
},
5102 {"ScrollHistoryPageDown", cmdScrollHistoryPageDown
},
5103 {"ScrollHistoryTop", cmdScrollHistoryTop
},
5104 {"ScrollHistoryBottom", cmdScrollHistoryBottom
},
5105 {"UTF8Locale", cmdUTF8Locale
}, // 'on', 'off'
5106 {"AudibleBell", cmdAudibleBell
},
5107 {"CommandMode", cmdCommandMode
},
5108 {"Cursor", cmdCursor
},
5109 {"ResetAttrs", cmdResetAttrs
},
5110 {"ResetCharset", cmdResetCharset
},
5111 {"Screen", cmdScreen
},
5112 {"MouseReports", cmdMouseReports
},
5113 {"Monochrome", cmdMonochrome
},
5114 {"Mono", cmdMonochrome
},
5116 {"term", cmdTermName},
5117 {"title", cmdWinTitle},
5118 {"tabsize", cmdTabSize},
5119 {"defaultcursorfg", cmdDefaultCursorFG},
5120 {"defaultcursorbg", cmdDefaultCursorBG},
5121 {"defaultinactivecursorfg", cmdDefaultInactiveCursorFG},
5122 {"defaultinactivecursorbg", cmdDefaultInactiveCursorBG},
5123 {"defaultboldfg", cmdDefaultBoldFG},
5124 {"defaultunderlinefg", cmdDefaultUnderlineFG},
5130 // !0: NewTab command
5131 static int executeCommand (const char *str
, int slen
) {
5136 if (str
== NULL
) return 0;
5137 if (slen
< 0) slen
= strlen(str
);
5139 for (int f
= 0; f
< slen
; ++f
) if (!str
[f
]) { slen
= f
; break; }
5141 while (slen
> 0 && isspace(*str
)) { ++str
; --slen
; }
5142 if (slen
< 1 || !str
[0]) return 0;
5144 for (e
= str
; slen
> 0 && !isspace(*e
); ++e
, --slen
) ;
5146 if (e
-str
> 127) return 0;
5147 cmdname
= alloca(e
-str
+8);
5148 if (cmdname
== NULL
) return 0;
5149 memcpy(cmdname
, str
, e
-str
);
5151 if (opt_disabletabs
&& strcasecmp(cmdname
, "NewTab") == 0) return 1;
5153 while (slen
> 0 && isspace(*e
)) { ++e
; --slen
; }
5154 //FIXME: ugly copypaste!
5156 for (int f
= 0; commandList
[f
].name
!= NULL
; ++f
) {
5157 if (strcasecmp(commandList
[f
].name
, cmdname
) == 0) {
5158 char *left
= calloc(slen
+2, 1);
5161 if (slen
> 0) memcpy(left
, e
, slen
);
5162 //fprintf(stderr, "command: [%s]; args: [%s]\n", cmdname, left);
5163 commandList
[f
].fn(cmdname
, left
);
5172 char *left
= calloc(slen
+2, 1);
5175 if (slen
> 0) memcpy(left
, e
, slen
);
5176 processMiscCmds(cmdname
, left
);
5185 static void executeCommands (const char *str
) {
5187 oldTermIdx
= termidx
;
5190 if (str
== NULL
) return;
5195 while (*str
&& isspace(*str
)) ++str
;
5197 if (*str
== ';') { ++str
; continue; }
5202 if (*ce
== ';' && qch
== ' ') break;
5203 if (qch
!= ' ' && *ce
== qch
) { qch
= ' '; ++ce
; continue; }
5204 if (*ce
== '"' || *ce
== '\'') {
5205 if (qch
== ' ') qch
= *ce
;
5209 if (*ce
++ == '\\' && *ce
) ++ce
;
5212 if (executeCommand(str
, ce
-str
)) break;
5213 if (*ce
) str
= ce
+1; else break;
5216 switchToTerm(oldTermIdx
, 1);
5220 ////////////////////////////////////////////////////////////////////////////////
5221 int main (int argc
, char *argv
[]) {
5222 char *configfile
= NULL
;
5226 for (int f
= 1; f
< argc
; f
++) {
5227 if (strcmp(argv
[f
], "-name") == 0) { ++f
; continue; }
5228 if (strcmp(argv
[f
], "-into") == 0) { ++f
; continue; }
5229 if (strcmp(argv
[f
], "-embed") == 0) { ++f
; continue; }
5230 switch (argv
[f
][0] != '-' || argv
[f
][2] ? -1 : argv
[f
][1]) {
5231 case 'e': f
= argc
+1; break;
5241 opt_term
= strdup(argv
[f
]);
5242 opt_term_locked
= 1;
5247 if (configfile
!= NULL
) free(configfile
);
5248 configfile
= strdup(argv
[f
]);
5252 if (++f
< argc
) cliLocale
= argv
[f
];
5254 case 'S': // single-tab mode
5255 opt_disabletabs
= 1;
5260 fprintf(stderr
, "%s", USAGE
);
5265 initDefaultOptions();
5266 if (configfile
== NULL
) {
5267 const char *home
= getenv("HOME");
5270 configfile
= SPrintf("%s/.sterm.rc", home
);
5271 if (loadConfig(configfile
) == 0) goto cfgdone
;
5272 free(configfile
); configfile
= NULL
;
5274 configfile
= SPrintf("%s/.config/sterm.rc", home
);
5275 if (loadConfig(configfile
) == 0) goto cfgdone
;
5276 free(configfile
); configfile
= NULL
;
5279 configfile
= SPrintf("/etc/sterm.rc");
5280 if (loadConfig(configfile
) == 0) goto cfgdone
;
5281 free(configfile
); configfile
= NULL
;
5283 configfile
= SPrintf("/etc/sterm/sterm.rc");
5284 if (loadConfig(configfile
) == 0) goto cfgdone
;
5285 free(configfile
); configfile
= NULL
;
5287 configfile
= SPrintf("./.sterm.rc");
5288 if (loadConfig(configfile
) == 0) goto cfgdone
;
5289 free(configfile
); configfile
= NULL
;
5292 if (loadConfig(configfile
) < 0) die("config file '%s' not found!", configfile
);
5295 if (configfile
!= NULL
) free(configfile
); configfile
= NULL
;
5297 for (int f
= 1; f
< argc
; f
++) {
5298 if (strcmp(argv
[f
], "-name") == 0) { ++f
; continue; }
5299 if (strcmp(argv
[f
], "-into") == 0 || strcmp(argv
[f
], "-embed") == 0) {
5300 if (opt_embed
) free(opt_embed
);
5301 opt_embed
= strdup(argv
[f
]);
5304 switch (argv
[f
][0] != '-' || argv
[f
][2] ? -1 : argv
[f
][1]) {
5308 opt_title
= strdup(argv
[f
]);
5314 opt_class
= strdup(argv
[f
]);
5319 if (opt_embed
) free(opt_embed
);
5320 opt_embed
= strdup(argv
[f
]);
5324 /* eat every remaining arguments */
5325 if (++f
< argc
) opt_cmd
= &argv
[f
];
5337 fprintf(stderr
, "%s", USAGE
);
5342 setenv("TERM", opt_term
, 1);
5344 setlocale(LC_ALL
, "");
5349 if (term
->execcmd
!= NULL
) { free(term
->execcmd
); term
->execcmd
= NULL
; }
5350 tinitialize(80, 25);
5351 if (ttynew(term
) != 0) die("can't run process");