1 /* See LICENSE for licence details. */
2 #define VERSION "0.3.1.beta7"
7 #define _XOPEN_SOURCE 600
24 #include <sys/ioctl.h>
25 #include <sys/select.h>
28 #include <sys/types.h>
32 #include <X11/Xatom.h>
34 #include <X11/Xutil.h>
35 #include <X11/cursorfont.h>
36 #include <X11/keysym.h>
41 // uncomment the following to use XCreateGlyphCursor() instead of XCreatePixmapCursor()
42 //#define BLANKPTR_USE_GLYPH_CURSOR
45 //#define PASTE_SELECTION_DEBUG
47 //#define DUMP_KEYSYMS
51 //#define DUMP_PROG_OUTPUT
52 //#define DUMP_PROG_INPUT
54 //#define DUMP_IO_READ
55 //#define DUMP_IO_WRITE
57 #if defined(DUMP_IO_READ) || defined(DUMP_IO_WRITE)
62 # define DUMP_KEYPAD_SWITCH(strflag,strstate) do { fprintf(stderr, "KEYPAD %s (%s)\n", (strstate), (strflag)); } while (0)
64 # define DUMP_KEYPAD_SWITCH(strflag,strstate) ((void)(sizeof(strflag)+sizeof(strstate)))
68 ////////////////////////////////////////////////////////////////////////////////
70 "sterm " VERSION " (c) 2010-2012 st engineers and Ketmar // Vampire Avalon\n" \
71 "usage: sterm [-t title] [-c class] [-w windowid] [-T termname] [-C config_file] [-l langiconv] [-S] [-v] [-R stcmd] [-e command...]\n"
74 ////////////////////////////////////////////////////////////////////////////////
75 #define FONT "-*-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"
76 #define FONTBOLD "-*-fixed-bold-r-normal-*-18-*-*-*-*-*-*-*"
77 #define FONTTAB "-*-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*"
80 /* Default shell to use if SHELL is not set in the env */
81 #define SHELL "/bin/sh"
84 /* Terminal colors (16 first used in escape sequence) */
85 static const char *defcolornames
[] = {
106 /* 8 normal colors */
115 /* 8 bright colors */
128 /* more colors can be added after 255 to use with DefaultXX */
129 static const char *defextcolornames
[] = {
132 /* root terminal fg and bg */
135 /* bold and underline */
141 /* Default colors (colorname index) foreground, background, cursor, unfocused cursor */
142 #define DEFAULT_FG (7)
143 #define DEFAULT_BG (0)
144 #define DEFAULT_CS (256)
145 #define DEFAULT_UCS (257)
147 #define TNAME "xterm"
149 /* double-click timeout (in milliseconds) between clicks for selection */
150 #define DOUBLECLICK_TIMEOUT (300)
151 #define TRIPLECLICK_TIMEOUT (2*DOUBLECLICK_TIMEOUT)
152 //#define SELECT_TIMEOUT 20 /* 20 ms */
153 #define DRAW_TIMEOUT 18 /* 18 ms */
158 ////////////////////////////////////////////////////////////////////////////////
159 #define MAX_COLOR (511)
161 /* XEMBED messages */
162 #define XEMBED_FOCUS_IN (4)
163 #define XEMBED_FOCUS_OUT (5)
166 /* Arbitrary sizes */
167 #define ESC_TITLE_SIZ (256)
168 #define ESC_BUF_SIZ (256)
169 #define ESC_ARG_SIZ (16)
170 #define DRAW_BUF_SIZ (2048)
172 #define OBUFSIZ (256)
173 #define WBUFSIZ (256)
176 /* masks for key translation */
177 #define XK_NO_MOD (UINT_MAX)
178 #define XK_ANY_MOD (0)
181 /* misc utility macros */
182 //#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000+(t1.tv_usec-t2.tv_usec)/1000)
183 #define SERRNO strerror(errno)
184 #define MIN(a, b) ((a) < (b) ? (a) : (b))
185 #define MAX(a, b) ((a) < (b) ? (b) : (a))
186 #define LEN(a) (sizeof(a)/sizeof(a[0]))
187 #define DEFAULT(a, b) (a) = ((a) ? (a) : (b))
188 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
189 #define LIMIT(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x))
190 #define ATTRCMP(a, b) ((a).attr != (b).attr || (a).fg != (b).fg || (a).bg != (b).bg)
191 #define IS_SET(flag) (term->mode&(flag))
192 #define X2COL(x) ((x)/xw.cw)
193 #define Y2ROW(y) ((y-(opt_tabposition==1 ? xw.tabheight : 0))/xw.ch-(term!=NULL ? term->topline : 0))
194 #define IS_GFX(mode) ((mode)&ATTR_GFX)
197 ////////////////////////////////////////////////////////////////////////////////
209 enum glyph_attribute
{
212 ATTR_UNDERLINE
= 0x02,
219 enum cursor_movement
{
229 CURSOR_DEFAULT
= 0x00,
231 CURSOR_WRAPNEXT
= 0x02
235 GLYPH_SET
= 0x01, /* for selection only */
237 GLYPH_WRAP
= 0x10, /* can be set for the last line glyph */
244 MODE_APPKEYPAD
= 0x04,
245 MODE_ALTSCREEN
= 0x08,
247 MODE_MOUSEBTN
= 0x20,
248 MODE_MOUSEMOTION
= 0x40,
249 MODE_MOUSE
= 0x20|0x40,
251 MODE_BRACPASTE
= 0x100,
252 MODE_FOCUSEVT
= 0x200,
253 MODE_DISPCTRL
= 0x400, //TODO: not implemented yet
263 ESC_ALTCHARSET
= 0x10,
277 //enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 };
280 ////////////////////////////////////////////////////////////////////////////////
281 typedef unsigned char uchar
;
282 typedef unsigned int uint
;
283 typedef uint32_t uint32
;
284 typedef uint16_t ushort
;
287 typedef struct __attribute__((packed
)) {
289 union __attribute__((packed
)) {
293 uchar attr
; /* attribute flags */
294 ushort fg
; /* foreground */
295 ushort bg
; /* background */
296 uchar state
; /* state flags */
303 Glyph attr
; /* current char attributes */
310 /* CSI Escape sequence structs */
311 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
313 char buf
[ESC_BUF_SIZ
]; /* raw string */
314 int len
; /* raw string length */
316 int arg
[ESC_ARG_SIZ
];
317 int narg
; /* nb of args */
322 /* Purely graphic info */
333 int w
; /* window width */
334 int h
; /* window height */
335 int bufw
; /* pixmap width */
336 int bufh
; /* pixmap height */
337 int ch
; /* char height */
338 int cw
; /* char width */
339 char state
; /* focus, redraw, visible */
341 int tch
; /* tab text char height */
344 //struct timeval lastdraw;
348 /* TODO: use better name for vars... */
353 struct { int x
, y
; } b
, e
;
361 /* Drawing Context */
363 uint32
*ncol
; // normal colors
364 uint32
*bcol
; // b/w colors
365 uint32
*gcol
; // green colors
379 typedef void (*CmdLineExecFn
) (int cancelled
);
382 #define CMDLINE_SIZE (256)
384 /* Internal representation of the screen */
385 typedef struct Term
{
389 int needConv
; /* 0: utf-8 locale */
397 int curblinkinactive
;
399 int row
; /* nb row */
400 int col
; /* nb col */
401 int topline
; /* top line for drawing (0: no history; 1: show one history line; etc) */
402 int linecount
; /* full, with history */
403 int maxhistory
;/* max history lines; 0: none; <0: infinite */
404 Line
*line
; /* screen */
405 Line
*alt
; /* alternate screen */
406 char *dirty
; /* dirtyness of lines */
407 TCursor c
; /* cursor */
408 int top
; /* top scroll limit */
409 int bot
; /* bottom scroll limit */
410 int mode
; /* terminal mode flags */
411 int mousemode
; /* mouse mode: 1000, 1005, 1006, 1015 */
412 int esc
; /* escape state flags */
413 int charset
; /* 0 or 1 */
415 TCursor csaved
; /* saved cursor info */
416 // old cursor position
420 char title
[ESC_TITLE_SIZ
+1];
428 #ifdef DUMP_PROG_OUTPUT
436 char drawbuf
[DRAW_BUF_SIZ
];
462 char cmdline
[UTF_SIZ
*CMDLINE_SIZE
];
463 int cmdreslen
; // byte length of 'reserved' (read-only) part
465 char cmdc
[UTF_SIZ
+1];
468 const char *cmdprevc
;
469 CmdLineExecFn cmdexecfn
;
473 ////////////////////////////////////////////////////////////////////////////////
475 static ushort
*unimap
= NULL
; // 0 or 65535 items; 127: special; high bit set: use alt(gfx) mode; 0: empty
477 static char **opt_cmd
= NULL
;
478 static char *opt_title
= NULL
;
479 static char *opt_embed
= NULL
;
480 static char *opt_class
= NULL
;
481 static char *opt_term
= NULL
;
482 static char *opt_fontnorm
= NULL
;
483 static char *opt_fontbold
= NULL
;
484 static char *opt_fonttab
= NULL
;
485 static char *opt_shell
= NULL
;
486 static char *opt_colornames
[512];
487 static int opt_term_locked
= 0;
488 static int opt_doubleclick_timeout
= DOUBLECLICK_TIMEOUT
;
489 static int opt_tripleclick_timeout
= TRIPLECLICK_TIMEOUT
;
490 static int opt_tabsize
= TAB
;
491 static int defaultFG
= DEFAULT_FG
;
492 static int defaultBG
= DEFAULT_BG
;
493 static int defaultCursorFG
= 0;
494 static int defaultCursorBG
= DEFAULT_CS
;
495 static int defaultCursorInactiveFG
= 0;
496 static int defaultCursorInactiveBG
= DEFAULT_UCS
;
497 static int defaultBoldFG
= -1;
498 static int defaultUnderlineFG
= -1;
499 static int normalTabFG
= 258;
500 static int normalTabBG
= 257;
501 static int activeTabFG
= 258;
502 static int activeTabBG
= 0;
503 static int opt_maxhistory
= 512;
504 static int opt_ptrblank
= 2000; // delay; 0: never
505 static int opt_tabcount
= 6;
506 static int opt_tabposition
= 0; // 0: bottom; 1: top
507 static int opt_drawtimeout
= DRAW_TIMEOUT
;
508 static int opt_disabletabs
= 0;
509 static int opt_audiblebell
= 1;
510 static int opt_urgentbell
= 1;
511 static int opt_cursorBlink
= 0;
512 static int opt_cursorBlinkInactive
= 0;
513 static int opt_drawunderline
= 1;
514 static int opt_ignoreclose
= 0;
515 static int opt_maxdrawtimeout
= 3000;
516 static int opt_fastredraw
= 0;
517 static int ptrBlanked
= 0;
518 static int ptrLastMove
= 0;
519 static int globalBW
= 0;
521 static Term
**term_array
= NULL
;
522 static int term_count
= 0;
523 static int term_array_size
= 0;
524 static Term
*term
; // current terminal
525 static int termidx
; // current terminal index; DON'T RELAY ON IT!
526 static int updateTabBar
;
527 static int lastDrawTime
= 0;
528 static char *lastSelStr
= NULL
;
529 //static int lastSelLength = 0;
531 static int firstVisibleTab
= 0;
533 static int exitcode
= 0;
534 static int closeRequestComes
= 0;
539 static Atom XA_VT_SELECTION
;
540 static Atom XA_CLIPBOARD
;
542 static Atom XA_TARGETS
;
543 static Atom XA_NETWM_NAME
;
544 static Atom XA_WM_DELETE_WINDOW
;
547 ////////////////////////////////////////////////////////////////////////////////
554 static KeyTransDef
*keytrans
= NULL
;
555 static int keytrans_size
= 0;
556 static int keytrans_used
= 0;
567 static KeyInfoDef
*keybinds
= NULL
;
568 static int keybinds_size
= 0;
569 static int keybinds_used
= 0;
571 static KeyInfoDef
*keymap
= NULL
;
572 static int keymap_size
= 0;
573 static int keymap_used
= 0;
576 ////////////////////////////////////////////////////////////////////////////////
577 static void executeCommands (const char *str
);
578 static const char *findCommandCompletion (const char *str
, int slen
, const char *prev
);
580 static void ttyresize (void);
581 static void tputc (const char *c
); // `c` is utf-8
582 static void ttywrite (const char *s
, size_t n
);
583 static void ttywritenoenc (const char *s
, size_t n
);
584 static void tsetdirt (int top
, int bot
);
585 static void tfulldirt (void);
587 static void xclearunused (void);
588 static void xseturgency (int add
);
589 static void xfixsel (void);
590 static void xblankPointer (void);
591 static void xunblankPointer (void);
592 static void xdrawTabBar (void);
594 static void draw (int forced
);
596 static void tcmdput (const char *s
, int len
);
599 static inline void ttywritestr (const char *s
) { if (s
!= NULL
&& s
[0]) ttywrite(s
, strlen(s
)); }
600 static inline void ttywritestrnoenc (const char *s
) { if (s
!= NULL
&& s
[0]) ttywritenoenc(s
, strlen(s
)); }
603 static inline uint32
getColor (int idx
) {
604 if (globalBW
&& (term
== NULL
|| !term
->blackandwhite
)) return dc
.clrs
[globalBW
][idx
];
605 if (term
!= NULL
) return dc
.clrs
[term
->blackandwhite
%3][idx
];
606 return dc
.clrs
[0][idx
];
610 ////////////////////////////////////////////////////////////////////////////////
612 static void trimstr (char *s
) {
615 while (*s
&& isspace(*s
)) ++s
;
616 for (e
= s
+strlen(s
); e
> s
; --e
) if (!isspace(e
[-1])) break;
617 if (e
<= s
) *s
= 0; else *e
= 0;
621 // parse the argument list
622 // return error message or NULL
625 // 's': string (char *)
626 // 'i': integer (int *)
627 // 'b': boolean (int *)
628 // '|': optional arguments follows
629 // '.': stop parsing, ignore rest
630 // 'R': stop parsing, set rest ptr (char *)
631 // string modifiers (also for 'R'):
632 // '!' -- don't allow empty strings
633 // '-' -- trim spaces
638 // WARNING! `line` will be modified!
639 // WARNING! string pointers will point INSIDE `line`, so don't discard it!
641 const char *iniParseArguments (char *line
, const char *fmt
, ...) {
645 if (line
== NULL
) return "alas";
649 char spec
= *fmt
++, *args
;
651 if (spec
== '|') { inOptional
= 1; continue; }
652 if (spec
== '.') { va_end(ap
); return NULL
; }
654 while (*line
&& isspace(*line
)) ++line
;
655 if (*line
== '#') *line
= 0;
658 char **p
= va_arg(ap
, char **);
662 if (*fmt
== '!') { ++fmt
; noempty
= 1; }
663 else if (*fmt
== '-') { ++fmt
; trimstr(line
); }
667 if (noempty
&& !line
[0]) return "invalid empty arg";
668 if (p
!= NULL
) *p
= line
;
673 // end of line, stop right here
675 if (!inOptional
) return "out of args";
681 char *dest
= args
, qch
= '#';
684 if (line
[0] == '"' || line
[0] == '\'') qch
= *line
++;
686 while (*line
&& *line
!= qch
) {
687 if (qch
== '#' && isspace(*line
)) break;
690 if (!line
[1]) { va_end(ap
); return "invalid escape"; }
692 case 'n': *dest
++ = '\n'; ++line
; break;
693 case 'r': *dest
++ = '\r'; ++line
; break;
694 case 't': *dest
++ = '\t'; ++line
; break;
695 case 'a': *dest
++ = '\a'; ++line
; break;
696 case 'e': *dest
++ = '\x1b'; ++line
; break; // esc
697 case 's': *dest
++ = ' '; ++line
; break;
700 if (!isxdigit(*line
)) { va_end(ap
); return "invalid hex escape"; }
701 n
= toupper(*line
)-'0'; if (n
> 9) n
-= 7;
703 if (isxdigit(*line
)) {
704 int b
= toupper(*line
)-'0'; if (b
> 9) b
-= 7;
713 for (int f
= 0; f
< 4; ++f
) {
714 if (*line
< '0' || *line
> '7') break;
715 n
= (n
*8)+(line
[0]-'0');
716 if (n
> 255) { va_end(ap
); return "invalid oct escape"; }
719 if (n
== 0) { va_end(ap
); return "invalid oct escape"; }
722 case '1'...'9': // decimal
724 for (int f
= 0; f
< 3; ++f
) {
725 if (*line
< '0' || *line
> '9') break;
726 n
= (n
*8)+(line
[0]-'0');
727 if (n
> 255) { va_end(ap
); return "invalid dec escape"; }
730 if (n
== 0) { va_end(ap
); return "invalid oct escape"; }
742 if (*line
!= qch
) return "unfinished string";
744 } else if (*line
&& *line
!= '#') ++line
;
747 // now process and convert argument
751 case 's': { /* string */
752 int noempty
= 0, trim
= 0;
756 if (*fmt
== '!') { noempty
= 1; ++fmt
; }
757 else if (*fmt
== '-') { trim
= 1; ++fmt
; }
761 if (trim
) trimstr(args
);
763 if (noempty
&& !args
[0]) { va_end(ap
); return "invalid empty string"; }
764 p
= va_arg(ap
, char **);
765 if (p
!= NULL
) *p
= args
;
770 return "invalid integer";
772 int *p
= va_arg(ap
, int *);
777 n
= strtol(args
, &eptr
, 0);
778 if (*eptr
) { va_end(ap
); return "invalid integer"; }
782 int minmax
[2], haveminmax
[2];
784 haveminmax
[0] = haveminmax
[1] = 0;
785 minmax
[0] = minmax
[1] = 0;
787 for (int f
= 0; f
< 2; ++f
) {
788 if (isdigit(*fmt
) || *fmt
== '-' || *fmt
== '+') {
791 if (*fmt
== '-') neg
= 1;
792 if (!isdigit(*fmt
)) {
794 if (!isdigit(*fmt
)) { va_end(ap
); return "invalid integer bounds"; }
796 while (isdigit(*fmt
)) {
797 minmax
[f
] = minmax
[f
]*10+(fmt
[0]-'0');
800 if (neg
) minmax
[f
] = -minmax
[f
];
801 //fprintf(stderr, "got: %d\n", minmax[f]);
804 if (f
== 1) { va_end(ap
); return "invalid integer bounds: extra comma"; }
805 // do nothing, we are happy
807 } else if (*fmt
== '}') {
810 } else { va_end(ap
); return "invalid integer bounds"; }
812 if (*fmt
!= '}') { va_end(ap
); return "invalid integer bounds"; }
815 //fprintf(stderr, "b: (%d,%d) (%d,%d)\n", haveminmax[0], minmax[0], haveminmax[1], minmax[1]);
816 if ((haveminmax
[0] && n
< minmax
[0]) || (haveminmax
[1] && n
> minmax
[1])) { va_end(ap
); return "integer out of bounds"; }
822 case 'b': { /* bool */
823 int *p
= va_arg(ap
, int *);
826 if (!args
[0]) { va_end(ap
); return "invalid boolean"; }
827 if (strcasecmp(args
, "true") == 0 || strcasecmp(args
, "on") == 0 ||
828 strcasecmp(args
, "tan") == 0 || strcasecmp(args
, "1") == 0 ||
829 strcasecmp(args
, "yes") == 0) {
831 } else if (strcasecmp(args
, "false") == 0 || strcasecmp(args
, "off") == 0 ||
832 strcasecmp(args
, "ona") == 0 || strcasecmp(args
, "0") == 0 ||
833 strcasecmp(args
, "no") == 0) {
837 return "invalid boolean";
842 return "invalid format specifier";
846 while (*line
&& isspace(*line
)) ++line
;
847 if (!line
[0] || line
[0] == '#') return NULL
;
852 ////////////////////////////////////////////////////////////////////////////////
855 static const unsigned char utf8Length
[256] = {
856 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x00-0x0f
857 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x10-0x1f
858 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x20-0x2f
859 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x30-0x3f
860 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x40-0x4f
861 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x50-0x5f
862 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x60-0x6f
863 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //0x70-0x7f
864 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0x80-0x8f
865 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0x90-0x9f
866 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0xa0-0xaf
867 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0xb0-0xbf
868 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, //0xc0-0xcf c0-c1: overlong encoding: start of a 2-byte sequence, but code point <= 127
869 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, //0xd0-0xdf
870 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, //0xe0-0xef
871 4,4,4,4,4,8,8,8,8,8,8,8,8,8,8,8 //0xf0-0xff
875 // decode one utf-8 char from *buf to *u, return char length; return '?' on error
876 static int utf8decode (uint32
*u
, const void *buf
) {
877 const unsigned char *data
= (const unsigned char *)buf
;
878 unsigned char len
= utf8Length
[*data
];
885 case 8: case 9: // invalid
890 uc
= (*data
++)&(0x7c>>len
);
892 if (utf8Length
[*data
] != 9) { uc
= 0xffff; break; }
893 uc
= (uc
<<6)|((*data
++)&0x3f);
895 if (uc
> 0x10ffff) uc
&= 0x1fffff;
896 if ((uc
>= 0xd800 && uc
<= 0xdfff) || // utf16/utf32 surrogates
897 (uc
>= 0xfdd0 && uc
<= 0xfdef) || // just for fun
898 (uc
>= 0xfffe && uc
<= 0xffff)) uc
= '?'; // bad unicode
900 return data
-((const unsigned char *)buf
);
904 // encode one utf-8 char from u to *buf, return char length
905 static int utf8encode (void *buf
, uint32 uc
) {
906 uchar
*sp
= (uchar
*)buf
;
909 if (uc
< 0x80) { *sp
= uc
; return 1; } /* 0xxxxxxx */
913 *sp
++ = (uc
>>6)|0xc0;
915 } else if (uc
< 0x10000) {
917 *sp
++ = (uc
>>12)|0xe0;
919 } else if (uc
<= 0x10FFFF) {
921 *sp
++ = (uc
>>18)|0xf0;
925 memcpy(sp
, "\xEF\xBF\xBD", 3);
928 for (int f
= n
; f
> 0; --f
) *sp
++ = ((uc
>>(6*(f
-1)))&0x3f)|0x80; /* 10xxxxxx */
933 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
934 UTF-8 otherwise return 0 */
935 static int isfullutf8 (const void *buf
, int buflen
) {
937 const unsigned char *data
= (const unsigned char *)buf
;
938 unsigned char len
= utf8Length
[*data
++];
942 case 0: case 8: case 9: return 1;
944 if ((res
= (buflen
>= len
))) buflen
= len
;
945 for (int f
= buflen
-1; f
> 0; --f
) if (((*data
++)&0xc0) != 0x80) return 1;
952 static inline int utf8size (const void *buf
) {
953 const unsigned char *data
= (const unsigned char *)buf
;
954 unsigned char len
= utf8Length
[*data
];
958 case 8: case 9: return 0;
964 static int utf8strlen (const char *s
) {
968 if (((unsigned char)(s
[0])&0xc0) == 0xc0 || ((unsigned char)(s
[0])&0x80) == 0) ++len
;
975 static void utf8choplast (char *s
) {
978 for (char *t
= s
; *t
; ++t
) {
979 if (((unsigned char)(t
[0])&0xc0) == 0xc0 || ((unsigned char)(t
[0])&0x80) == 0) lastpos
= (int)(t
-s
);
985 ////////////////////////////////////////////////////////////////////////////////
987 static char *SPrintfVA (const char *fmt
, va_list vaorig
) {
992 if (buf
== NULL
) { fprintf(stderr
, "\nFATAL: out of memory!\n"); abort(); }
998 olen
= vsnprintf(buf
, len
, fmt
, va
);
1000 if (olen
>= 0 && olen
< len
) return buf
;
1001 if (olen
< 0) olen
= len
*2-1;
1002 nb
= realloc(buf
, olen
+1);
1003 if (nb
== NULL
) { fprintf(stderr
, "\nFATAL: out of memory!\n"); abort(); }
1010 static __attribute__((format(printf
,1,2))) char *SPrintf (const char *fmt
, ...) {
1015 buf
= SPrintfVA(fmt
, va
);
1021 static __attribute__((noreturn
)) __attribute__((format(printf
,1,2))) void die (const char *errstr
, ...) {
1024 fprintf(stderr
, "FATAL: ");
1025 va_start(ap
, errstr
);
1026 vfprintf(stderr
, errstr
, ap
);
1028 fprintf(stderr
, "\n");
1033 ////////////////////////////////////////////////////////////////////////////////
1035 static struct timespec mclk_sttime
; // starting time of monotonic clock
1038 static void mclock_init (void) {
1039 clock_gettime(CLOCK_MONOTONIC
/*CLOCK_MONOTONIC_RAW*/, &mclk_sttime
);
1043 static int mclock_ticks (void) {
1046 clock_gettime(CLOCK_MONOTONIC
/*CLOCK_MONOTONIC_RAW*/, &tp
);
1047 tp
.tv_sec
-= mclk_sttime
.tv_sec
;
1048 return tp
.tv_sec
*1000+(tp
.tv_nsec
/1000000);
1052 ////////////////////////////////////////////////////////////////////////////////
1053 // locale conversions
1054 static iconv_t icFromLoc
;
1055 static iconv_t icToLoc
;
1056 static int needConversion
= 0;
1057 static const char *cliLocale
= NULL
;
1060 static void initLCConversion (void) {
1061 const char *lct
= setlocale(LC_CTYPE
, NULL
);
1065 if (cliLocale
== NULL
) {
1066 if (strrchr(lct
, '.') != NULL
) lct
= strrchr(lct
, '.')+1;
1070 if (strcasecmp(lct
, "utf8") == 0 || strcasecmp(lct
, "utf-8") == 0) return;
1071 //fprintf(stderr, "locale: [%s]\n", lct);
1072 icFromLoc
= iconv_open("UTF-8", lct
);
1073 if (icFromLoc
== (iconv_t
)-1) die("can't initialize locale conversion");
1074 cstr
= SPrintf("%s//TRANSLIT", lct
);
1075 icToLoc
= iconv_open(cstr
, "UTF-8");
1077 if (icToLoc
== (iconv_t
)-1) die("can't initialize locale conversion");
1082 static int loc2utf (char *dest
, const char *src
, int len
) {
1083 if (needConversion
) {
1091 il
= iconv(icFromLoc
, &ibuf
, &il
, &obuf
, &ol
);
1092 if (il
== (size_t)-1) return 0;
1095 if (len
> 0) memmove(dest
, src
, len
);
1101 static int utf2loc (char *dest
, const char *src
, int len
) {
1102 if (needConversion
) {
1110 il
= iconv(icToLoc
, &ibuf
, &il
, &obuf
, &ol
);
1111 if (il
== (size_t)-1) return 0;
1114 if (len
> 0) memmove(dest
, src
, len
);
1120 ////////////////////////////////////////////////////////////////////////////////
1121 static void fixWindowTitle (const Term
*t
) {
1122 const char *title
= (t
!= NULL
) ? t
->title
: NULL
;
1124 if (title
== NULL
|| !title
[0]) {
1126 if (title
== NULL
) title
= "";
1128 XStoreName(xw
.dpy
, xw
.win
, title
);
1129 XChangeProperty(xw
.dpy
, xw
.win
, XA_NETWM_NAME
, XA_UTF8
, 8, PropModeReplace
, (const unsigned char *)title
, strlen(title
));
1133 // find latest active terminal (but not current %-)
1134 static int findTermToSwitch (void) {
1135 int maxlat
= -1, idx
= -1;
1137 for (int f
= 0; f
< term_count
; ++f
) {
1138 if (term
!= term_array
[f
] && term_array
[f
]->lastActiveTime
> maxlat
) {
1139 maxlat
= term_array
[f
]->lastActiveTime
;
1144 if (termidx
== 0) idx
= 0; else idx
= termidx
+1;
1145 if (idx
> term_count
) idx
= term_count
-1;
1151 static void fixFirstTab (void) {
1152 if (termidx
< firstVisibleTab
) firstVisibleTab
= termidx
;
1153 else if (termidx
> firstVisibleTab
+opt_tabcount
-1) firstVisibleTab
= termidx
-opt_tabcount
+1;
1154 if (firstVisibleTab
< 0) firstVisibleTab
= 0;
1159 static void switchToTerm (int idx
, int redraw
) {
1160 if (idx
>= 0 && idx
< term_count
&& term_array
[idx
] != NULL
&& term_array
[idx
] != term
) {
1161 int tt
= mclock_ticks();
1163 if (term
!= NULL
) term
->lastActiveTime
= tt
;
1165 term
= term_array
[termidx
];
1166 term
->curbhidden
= 0;
1167 term
->lastBlinkTime
= tt
;
1173 fixWindowTitle(term
);
1175 XSetWindowBackground(xw
.dpy
, xw
.win
, getColor(term
->defbg
));
1177 if (redraw
) draw(1);
1178 //FIXME: optimize memory allocations
1179 if (term
->sel
.clip
!= NULL
&& term
->sel
.bx
>= 0) {
1180 if (lastSelStr
!= NULL
) free(lastSelStr
);
1181 lastSelStr
= strdup(term
->sel
.clip
);
1182 //fprintf(stderr, "newsel: [%s]\n", lastSelStr);
1185 //fprintf(stderr, "term #%d\n", termidx);
1186 //fprintf(stderr, "needConv: %d\n", term->needConv);
1191 static Term
*termalloc (void) {
1194 if (term_count
>= term_array_size
) {
1195 int newsz
= (term_count
==0) ? 1 : term_array_size
+64;
1196 Term
**n
= realloc(term_array
, sizeof(Term
*)*newsz
);
1198 if (n
== NULL
&& term_count
== 0) die("out of memory!");
1200 term_array_size
= newsz
;
1202 if ((t
= calloc(1, sizeof(Term
))) == NULL
) return NULL
;
1203 t
->wrbufsize
= WBUFSIZ
;
1204 t
->deffg
= defaultFG
;
1205 t
->defbg
= defaultBG
;
1207 t
->needConv
= (needConversion
? 1 : 0);
1208 t
->belltype
= (opt_audiblebell
?BELL_AUDIO
:0)|(opt_urgentbell
?BELL_URGENT
:0);
1209 t
->curblink
= opt_cursorBlink
;
1210 t
->curblinkinactive
= opt_cursorBlinkInactive
;
1211 t
->fastredraw
= opt_fastredraw
;
1212 term_array
[term_count
++] = t
;
1217 // newer delete last terminal!
1218 static void termfree (int idx
) {
1219 if (idx
>= 0 && idx
< term_count
&& term_array
[idx
] != NULL
) {
1220 Term
*t
= term_array
[idx
];
1223 kill(t
->pid
, SIGKILL
);
1226 if (t
->cmdfd
>= 0) {
1230 exitcode
= t
->exitcode
;
1231 if (idx
== termidx
) {
1232 if (term_count
> 1) {
1234 //switchToTerm((idx > 0) ? idx-1 : 1, 0);
1235 switchToTerm(findTermToSwitch(), 0);
1240 for (int y
= 0; y
< t
->row
; ++y
) free(t
->alt
[y
]);
1241 for (int y
= 0; y
< t
->linecount
; ++y
) {
1242 //fprintf(stderr, "y=%d\n", y);
1248 if (t
->execcmd
!= NULL
) free(t
->execcmd
);
1250 if (termidx
> idx
) {
1251 // not current, and current at the right
1254 for (int f
= idx
+1; f
< term_count
; ++f
) term_array
[f
-1] = term_array
[f
];
1256 XFreePixmap(xw
.dpy
, t
->picbuf
);
1262 static void termcleanup (void) {
1263 int f
= 0, needredraw
= 0;
1265 while (f
< term_count
) {
1266 if (term_array
[f
]->dead
) {
1280 //FIXME: is it safe to assume that signal interrupted main program?
1281 static void sigchld (int a
) {
1282 //if (waitpid(term->pid, &stat, 0) < 0) die("waiting for pid %hd failed: %s", term->pid, SERRNO);
1285 pid_t res
= waitpid(-1, &stat
, WNOHANG
);
1287 if (res
== (pid_t
)-1 || res
== 0) break;
1289 for (int f
= 0; f
< term_count
; ++f
) {
1290 if (term_array
[f
]->pid
== res
) {
1291 // this terminal should die
1292 //if (term_count == 1) exit(0);
1293 //close(term_array[f]->cmdfd);
1294 //term_array[f]->cmdfd = -1;
1295 term_array
[f
]->dead
= 1;
1296 term_array
[f
]->pid
= 0;
1297 term_array
[f
]->exitcode
= (WIFEXITED(stat
)) ? WEXITSTATUS(stat
) : 127;
1298 //fprintf(stderr, "exitcode=%d\n", term_array[f]->exitcode);
1304 signal(SIGCHLD
, sigchld
);
1308 ////////////////////////////////////////////////////////////////////////////////
1309 static void keytrans_reset (void) {
1310 if (keytrans
) free(keytrans
);
1317 static void keytrans_add (const char *src
, const char *dst
) {
1318 KeySym kssrc
= XStringToKeysym(src
);
1319 KeySym ksdst
= XStringToKeysym(dst
);
1321 if (kssrc
== NoSymbol
) die("invalid keysym: '%s'", src
);
1322 if (ksdst
== NoSymbol
) die("invalid keysym: '%s'", dst
);
1323 if (kssrc
== ksdst
) return; // idiot
1325 for (int f
= 0; f
< keytrans_used
; ++f
) {
1326 if (keytrans
[f
].src
== kssrc
) {
1328 keytrans
[f
].dst
= ksdst
;
1333 if (keytrans_used
>= keytrans_size
) {
1334 int newsize
= keytrans_size
+64;
1335 KeyTransDef
*n
= realloc(keytrans
, sizeof(KeyTransDef
)*newsize
);
1337 if (n
== NULL
) die("out of memory");
1338 keytrans_size
= newsize
;
1341 keytrans
[keytrans_used
].src
= kssrc
;
1342 keytrans
[keytrans_used
].dst
= ksdst
;
1347 ////////////////////////////////////////////////////////////////////////////////
1348 static void parsekeyname (const char *str
, KeySym
*ks
, uint
*mask
, int *kp
) {
1349 char *s
= alloca(strlen(str
)+1);
1351 if (s
== NULL
) die("out of memory");
1362 while (*s
&& isspace(*s
)) ++s
;
1363 for (e
= s
; *e
&& !isspace(*e
) && *e
!= '+'; ++e
) ;
1366 if (strcasecmp(s
, "alt") == 0) mm
= Mod1Mask
;
1367 else if (strcasecmp(s
, "win") == 0) mm
= Mod4Mask
;
1368 else if (strcasecmp(s
, "ctrl") == 0) mm
= ControlMask
;
1369 else if (strcasecmp(s
, "shift") == 0) mm
= ShiftMask
;
1370 else if (strcasecmp(s
, "any") == 0) mm
= XK_NO_MOD
; //!
1371 else if (strcasecmp(s
, "kpad") == 0) *kp
= 1;
1374 if ((*ks
= XStringToKeysym(s
)) == NoSymbol
) break;
1375 //fprintf(stderr, "[%s]\n", s);
1380 while (*s
&& isspace(*s
)) ++s
;
1382 if (*s
!= '+') { *ks
= NoSymbol
; break; }
1385 if (mm
== XK_NO_MOD
) *mask
= XK_ANY_MOD
;
1386 else if (*mask
== XK_NO_MOD
) *mask
= mm
;
1387 else if (*mask
!= XK_ANY_MOD
) *mask
|= mm
;
1390 if (*s
) { *ks
= NoSymbol
; break; }
1393 if (*ks
== NoSymbol
) die("invalid key name: '%s'", str
);
1394 //fprintf(stderr, "mask=0x%08x, kp=%d\n", *mask, *kp);
1398 ////////////////////////////////////////////////////////////////////////////////
1399 static void keybinds_reset (void) {
1400 if (keybinds
) free(keybinds
);
1407 static void keybind_add (const char *key
, const char *act
) {
1412 parsekeyname(key
, &ks
, &mask
, &kp
);
1414 for (int f
= 0; f
< keybinds_used
; ++f
) {
1415 if (keybinds
[f
].key
== ks
&& keybinds
[f
].mask
== mask
) {
1416 // replace or remove
1417 free(keybinds
[f
].str
);
1418 if (act
== NULL
|| !act
[0]) {
1420 for (int c
= f
+1; c
< keybinds_used
; ++c
) keybinds
[c
-1] = keybinds
[c
];
1423 if ((keybinds
[f
].str
= strdup(act
)) == NULL
) die("out of memory");
1429 if (keybinds_used
>= keybinds_size
) {
1430 int newsize
= keybinds_size
+64;
1431 KeyInfoDef
*n
= realloc(keybinds
, sizeof(KeyInfoDef
)*newsize
);
1433 if (n
== NULL
) die("out of memory");
1434 keybinds_size
= newsize
;
1437 keybinds
[keybinds_used
].key
= ks
;
1438 keybinds
[keybinds_used
].mask
= mask
;
1439 keybinds
[keybinds_used
].kp
= 0;
1440 if ((keybinds
[keybinds_used
].str
= strdup(act
)) == NULL
) die("out of memory");
1445 ////////////////////////////////////////////////////////////////////////////////
1446 static void keymap_reset (void) {
1447 if (keymap
) free(keymap
);
1454 static void keymap_add (const char *key
, const char *act
) {
1459 parsekeyname(key
, &ks
, &mask
, &kp
);
1461 for (int f
= 0; f
< keymap_used
; ++f
) {
1462 if (keymap
[f
].key
== ks
&& keymap
[f
].mask
== mask
&& keymap
[f
].kp
== kp
) {
1463 // replace or remove
1464 free(keymap
[f
].str
);
1467 for (int c
= f
+1; c
< keymap_used
; ++c
) keymap
[c
-1] = keymap
[c
];
1470 if ((keymap
[f
].str
= strdup(act
)) == NULL
) die("out of memory");
1476 if (keymap_used
>= keymap_size
) {
1477 int newsize
= keymap_size
+128;
1478 KeyInfoDef
*n
= realloc(keymap
, sizeof(KeyInfoDef
)*newsize
);
1480 if (n
== NULL
) die("out of memory");
1481 keymap_size
= newsize
;
1484 keymap
[keymap_used
].key
= ks
;
1485 keymap
[keymap_used
].mask
= mask
;
1486 keymap
[keymap_used
].kp
= kp
;
1487 if ((keymap
[keymap_used
].str
= strdup(act
)) == NULL
) die("out of memory");
1492 ////////////////////////////////////////////////////////////////////////////////
1494 static inline void setWantRedraw (void) {
1496 term
->wantRedraw
= 1;
1497 if (!term
->fastredraw
) term
->lastDrawTime
= mclock_ticks(); //FIXME: avoid excess redraw?
1502 static void inline markDirty (int lineno
, int flag
) {
1503 if (term
!= NULL
&& lineno
>= 0 && lineno
< term
->row
) {
1504 term
->dirty
[lineno
] |= flag
;
1505 term
->wantRedraw
= 1;
1506 if (!term
->fastredraw
) term
->lastDrawTime
= mclock_ticks(); //FIXME: avoid excess redraw?
1511 static void selinit (void) {
1512 term
->sel
.tclick1
= term
->sel
.tclick2
= mclock_ticks();
1515 term
->sel
.clip
= NULL
;
1516 term
->sel
.xtarget
= XA_UTF8
;
1517 //if (term->sel.xtarget == None) term->sel.xtarget = XA_STRING;
1521 static void selhide (void) {
1522 if (term
->sel
.bx
!= -1) {
1530 static inline int selected (int x
, int y
) {
1531 if (term
->sel
.bx
== -1) return 0;
1533 if (y
>= term
->row
) y
= 0-(y
-term
->row
+1);
1534 if (term
->sel
.ey
== y
&& term
->sel
.by
== y
) {
1535 int bx
= MIN(term
->sel
.bx
, term
->sel
.ex
);
1536 int ex
= MAX(term
->sel
.bx
, term
->sel
.ex
);
1538 return BETWEEN(x
, bx
, ex
);
1542 ((term
->sel
.b
.y
< y
&& y
< term
->sel
.e
.y
) || (y
== term
->sel
.e
.y
&& x
<= term
->sel
.e
.x
)) ||
1543 (y
== term
->sel
.b
.y
&& x
>= term
->sel
.b
.x
&& (x
<= term
->sel
.e
.x
|| term
->sel
.b
.y
!= term
->sel
.e
.y
));
1547 static void getbuttoninfo (XEvent
*e
, int *b
, int *x
, int *y
) {
1548 if (b
!= NULL
) *b
= e
->xbutton
.button
;
1549 if (x
!= NULL
) *x
= X2COL(e
->xbutton
.x
);
1550 if (y
!= NULL
) *y
= Y2ROW(e
->xbutton
.y
);
1551 term
->sel
.b
.x
= (term
->sel
.by
< term
->sel
.ey
? term
->sel
.bx
: term
->sel
.ex
);
1552 term
->sel
.b
.y
= MIN(term
->sel
.by
, term
->sel
.ey
);
1553 term
->sel
.e
.x
= (term
->sel
.by
< term
->sel
.ey
? term
->sel
.ex
: term
->sel
.bx
);
1554 term
->sel
.e
.y
= MAX(term
->sel
.by
, term
->sel
.ey
);
1558 static void mousereport (XEvent
*e
) {
1559 int x
= X2COL(e
->xbutton
.x
);
1560 int y
= Y2ROW(e
->xbutton
.y
);
1561 int button
= e
->xbutton
.button
;
1562 int state
= e
->xbutton
.state
;
1563 //char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
1568 if (term
== NULL
) return;
1569 if (x
< 0 || x
>= term
->col
|| y
< 0 || y
>= term
->row
) return;
1571 //fprintf(stderr, "mr(%d): x=%d; y=%d\n", term->mousemode, x, y);
1573 case 1000: /* X11 xterm mouse reporting */
1574 case 1005: /* utf-8 mouse encoding */
1575 case 1006: /* sgr mouse encoding */
1576 case 1015: /* urxvt mouse encoding */
1578 //sprintf(buf, "\x1b[M%c%c%c", 0, 32+x+1, 32+y+1);
1579 p
= buf
+sprintf(buf
, "\x1b[M");
1581 if (e
->xbutton
.type
== MotionNotify
) {
1582 if (!IS_SET(MODE_MOUSEMOTION
) || (x
== term
->mouseox
&& y
== term
->mouseoy
)) return;
1583 button
= term
->mouseob
+32;
1586 } else if (e
->xbutton
.type
== ButtonRelease
|| button
== AnyButton
) {
1587 if (term
->mousemode
!= 1006) {
1588 button
= 3; // 'release' flag
1592 if (button
>= 3) button
+= 64-3;
1596 if (button
>= 3) button
+= 64-3;
1597 if (e
->xbutton
.type
== ButtonPress
) {
1598 term
->mouseob
= button
;
1603 ss
= (state
& ShiftMask
? 4 : 0)+(state
& Mod4Mask
? 8 : 0)+(state
& ControlMask
? 16 : 0);
1604 switch (term
->mousemode
) {
1605 case 1006: /* sgr */
1607 p
+= sprintf(p
, "%d;", button
+ss
);
1609 case 1015: /* urxvt */
1611 p
+= sprintf(p
, "%d;", 32+button
+ss
);
1614 *p
++ = 32+button
+ss
;
1618 switch (term
->mousemode
) {
1619 case 1005: /* utf-8 */
1620 p
+= utf8encode(p
, x
+1);
1621 p
+= utf8encode(p
, y
+1);
1623 case 1006: /* sgr */
1624 p
+= sprintf(p
, "%d;%d%c", x
+1, y
+1, lastCh
);
1626 case 1015: /* urxvt */
1627 p
+= sprintf(p
, "%d;%dM", x
+1, y
+1);
1630 p
+= sprintf(p
, "%c%c", 32+x
+1, 32+y
+1);
1636 fprintf(stderr, "(%d)<", term->mousemode);
1637 for (const unsigned char *s = (const unsigned char *)buf; *s; ++s) {
1638 if (s[0] < 32) fprintf(stderr, "{%d}", s[0]); else fputc(s[0], stderr);
1640 fputs(">\n", stderr);
1643 ttywritestrnoenc(buf
);
1647 static void xfixsel (void) {
1648 if (lastSelStr
!= NULL
) {
1649 XSetSelectionOwner(xw
.dpy
, XA_PRIMARY
, xw
.win
, CurrentTime
);
1650 XSetSelectionOwner(xw
.dpy
, XA_CLIPBOARD
, xw
.win
, CurrentTime
);
1652 if (XGetSelectionOwner(xw
.dpy
, XA_PRIMARY
) == xw
.win
) XSetSelectionOwner(xw
.dpy
, XA_PRIMARY
, None
, CurrentTime
);
1653 if (XGetSelectionOwner(xw
.dpy
, XA_CLIPBOARD
) == xw
.win
) XSetSelectionOwner(xw
.dpy
, XA_CLIPBOARD
, None
, CurrentTime
);
1659 static void xsetsel (char *str
) {
1660 /* register the selection for both the clipboard and the primary */
1661 if (term
== NULL
) return;
1662 if (term
->sel
.clip
!= NULL
) free(term
->sel
.clip
);
1663 term
->sel
.clip
= str
;
1664 if (lastSelStr
!= NULL
) free(lastSelStr
);
1665 lastSelStr
= (str
!= NULL
? strdup(str
) : NULL
);
1667 //fprintf(stderr, "[%s]\n", str);
1671 static void selclear (XEvent
*e
) {
1672 if (lastSelStr
!= NULL
) free(lastSelStr
);
1675 if (term
->sel
.clip
!= NULL
) free(term
->sel
.clip
);
1676 term
->sel
.clip
= NULL
;
1678 if (term
->sel
.bx
!= 0) {
1687 static Line
selgetlinebyy (int y
) {
1690 if (y
>= term
->row
) return NULL
;
1692 if (y
< -(term
->maxhistory
)) return NULL
;
1693 l
= term
->line
[term
->row
+(-y
)-1];
1701 static void selcopy (void) {
1703 int x
, y
, bufsize
, is_selected
= 0;
1705 if (term
== NULL
|| term
->sel
.bx
== -1) {
1708 int sy
= MIN(term
->sel
.e
.y
, term
->sel
.b
.y
);
1709 int ey
= MAX(term
->sel
.e
.y
, term
->sel
.b
.y
);
1712 fprintf(stderr, "bx=%d; ex=%d; by=%d; ey=%d\n", term->sel.bx, term->sel.by, term->sel.ex, term->sel.ey);
1713 fprintf(stderr, " b.x=%d; e.x=%d; b.y=%d; e.y=%d\n", term->sel.b.x, term->sel.b.y, term->sel.e.x, term->sel.e.y);
1714 fprintf(stderr, " sy=%d; ey=%d\n", sy, ey);
1716 if (ey
>= term
->row
) { selclear(NULL
); return; }
1717 bufsize
= (term
->col
+1)*(ey
-sy
+1)*UTF_SIZ
;
1718 ptr
= str
= malloc(bufsize
);
1719 /* append every set & selected glyph to the selection */
1720 for (y
= sy
; y
<= ey
; ++y
) {
1721 Line l
= selgetlinebyy(y
);
1724 if (l
== NULL
) continue;
1725 for (x
= 0; x
< term
->col
; ++x
) {
1726 if ((is_selected
= selected(x
, y
)) != 0) {
1727 int size
= utf8size(l
[x
].c
);
1729 //if (size == 1) fprintf(stderr, "x=%d; y=%d; size=%d; c=%d\n", x, y, size, l[x].c[0]);
1731 unsigned char c
= (unsigned char)l
[x
].c
[0];
1733 *ptr
= (c
< 32 || c
>= 127) ? ' ' : c
;
1734 } else if (size
> 0) {
1735 memcpy(ptr
, l
[x
].c
, size
);
1740 //fprintf(stderr, "y=%d; linebytes=%d\n", y, (int)(ptr-pstart));
1741 // trim trailing spaces
1742 while (ptr
> pstart
&& ptr
[-1] == ' ') --ptr
;
1743 // \n at the end of every unwrapped selected line except for the last one
1744 if (is_selected
&& y
< ey
&& selected(term
->col
-1, y
) && !(l
[term
->col
-1].state
&GLYPH_WRAP
)) *ptr
++ = '\n';
1749 if (!str
|| !str
[0]) selclear(NULL
);
1753 static void selnotify (XEvent
*e
) {
1754 unsigned long nitems
, ofs
, rem
;
1758 XSelectionEvent
*se
= (XSelectionEvent
*)e
;
1764 if (term
== NULL
|| term
->cmdMode
== CMDMODE_MESSAGE
) return;
1765 #ifdef PASTE_SELECTION_DEBUG
1769 fprintf(stderr
, "selnotify!\n");
1771 name
= se
->selection
!= None
? XGetAtomName(se
->display
, se
->selection
) : NULL
;
1772 fprintf(stderr
, " selection: [%s]\n", name
);
1773 if (name
!= NULL
) XFree(name
);
1775 name
= se
->target
!= None
? XGetAtomName(se
->display
, se
->target
) : NULL
;
1776 fprintf(stderr
, " target: [%s]\n", name
);
1777 if (name
!= NULL
) XFree(name
);
1779 name
= se
->property
!= None
? XGetAtomName(se
->display
, se
->property
) : NULL
;
1780 fprintf(stderr
, " property: [%s]\n", name
);
1781 if (name
!= NULL
) XFree(name
);
1785 if (se
->property
!= XA_VT_SELECTION
) return;
1787 #ifdef PASTE_SELECTION_DEBUG
1789 fprintf(stderr
, "selection:\n");
1790 fprintf(stderr
, " primary: %d\n", se
->selection
== XA_PRIMARY
);
1791 fprintf(stderr
, " secondary: %d\n", se
->selection
== XA_SECONDARY
);
1792 fprintf(stderr
, " clipboard: %d\n", se
->selection
== XA_CLIPBOARD
);
1793 fprintf(stderr
, " vtsel: %d\n", se
->selection
== XA_VT_SELECTION
);
1794 fprintf(stderr
, "target:\n");
1795 fprintf(stderr
, " primary: %d\n", se
->target
== XA_PRIMARY
);
1796 fprintf(stderr
, " secondary: %d\n", se
->target
== XA_SECONDARY
);
1797 fprintf(stderr
, " clipboard: %d\n", se
->target
== XA_CLIPBOARD
);
1798 fprintf(stderr
, " vtsel: %d\n", se
->target
== XA_VT_SELECTION
);
1801 if (se
->target
== XA_UTF8
) {
1803 } else if (se
->target
== XA_STRING
) {
1805 } else if (se
->target
== XA_TARGETS
) {
1806 Atom rqtype
= None
, *targ
;
1808 if (XGetWindowProperty(xw
.dpy
, xw
.win
, se
->property
, 0, 65536, False
, XA_ATOM
, &type
, &format
, &nitems
, &rem
, &data
)) {
1809 //fprintf(stderr, "no targets\n");
1812 for (targ
= (Atom
*)data
; nitems
> 0; --nitems
, ++targ
) {
1813 #ifdef PASTE_SELECTION_DEBUG
1814 fprintf(stderr
, " TGT: [%s]\n", XGetAtomName(se
->display
, *targ
));
1816 if (*targ
== XA_UTF8
) rqtype
= XA_UTF8
;
1817 else if (*targ
== XA_STRING
&& rqtype
== None
) rqtype
= XA_STRING
;
1821 if (rqtype
!= None
) XConvertSelection(xw
.dpy
, se
->selection
, rqtype
, XA_VT_SELECTION
, xw
.win
, CurrentTime
);
1832 if (XGetWindowProperty(xw
.dpy
, xw
.win
, se
->property
, ofs
, BUFSIZ
/4, False
, AnyPropertyType
, &type
, &format
, &nitems
, &rem
, &data
)) {
1833 fprintf(stderr
, "Clipboard allocation failed\n");
1836 //fprintf(stderr, "nitems=%d; format=%d; rem=%d\n", (int)nitems, format, (int)rem);
1837 blen
= nitems
*format
/8;
1840 int newsz
= blen
*4+64;
1842 if (ucbufsize
< newsz
) {
1843 char *n
= realloc(ucbuf
, newsz
);
1845 if (n
== NULL
) { XFree(data
); break; }
1850 blen
= loc2utf(ucbuf
, (const char *)data
, blen
);
1856 if (term
->cmdMode
!= CMDMODE_NONE
) {
1859 if (nitems
*format
/8 > 0 && !wasbrk
&& IS_SET(MODE_BRACPASTE
)) {
1861 ttywritestrnoenc("\x1b[200~");
1863 ttywrite(str
, blen
);
1866 /* number of 32-bit chunks returned */
1867 ofs
+= nitems
*format
/32;
1870 if (wasbrk
) ttywritestrnoenc("\x1b[201~");
1871 if (ucbuf
!= NULL
) free(ucbuf
);
1875 static void selpaste (Atom which
) {
1876 if (term
== NULL
) return;
1877 if (XGetSelectionOwner(xw
.dpy
, which
) == None
) return;
1878 //XConvertSelection(xw.dpy, which, term->sel.xtarget, XA_VT_SELECTION, xw.win, CurrentTime);
1879 XConvertSelection(xw
.dpy
, which
, XA_TARGETS
, XA_VT_SELECTION
, xw
.win
, CurrentTime
);
1881 if (which == XA_PRIMARY) selpaste(XA_SECONDARY);
1882 else if (which == XA_SECONDARY) selpaste(XA_CLIPBOARD);
1887 static void selrequest (XEvent
*e
) {
1888 XSelectionRequestEvent
*xsre
;
1889 XSelectionEvent xev
;
1891 if (lastSelStr
== NULL
) return;
1892 xsre
= (XSelectionRequestEvent
*)e
;
1893 xev
.type
= SelectionNotify
;
1894 xev
.requestor
= xsre
->requestor
;
1895 xev
.selection
= xsre
->selection
;
1896 xev
.target
= xsre
->target
;
1897 xev
.time
= xsre
->time
;
1899 xev
.property
= None
;
1900 if (xsre
->target
== XA_TARGETS
) {
1901 /* respond with the supported type */
1902 Atom tlist
[3] = {XA_UTF8
, XA_STRING
, XA_TARGETS
};
1904 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, XA_ATOM
, 32, PropModeReplace
, (uchar
*)tlist
, 3);
1905 xev
.property
= xsre
->property
;
1906 } else if (xsre
->target
== XA_UTF8
&& lastSelStr
!= NULL
) {
1907 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, XA_UTF8
, 8, PropModeReplace
, (uchar
*)lastSelStr
, strlen(lastSelStr
));
1908 xev
.property
= xsre
->property
;
1909 } else if (xsre
->target
== XA_STRING
&& lastSelStr
!= NULL
) {
1910 char *s
= malloc(strlen(lastSelStr
)*4+8);
1913 int len
= utf2loc(s
, lastSelStr
, strlen(lastSelStr
));
1915 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, XA_STRING
, 8, PropModeReplace
, (uchar
*)s
, len
);
1916 xev
.property
= xsre
->property
;
1920 /* all done, send a notification to the listener */
1921 if (!XSendEvent(xsre
->display
, xsre
->requestor
, True
, 0, (XEvent
*)&xev
)) fprintf(stderr
, "Error sending SelectionNotify event\n");
1925 static int x2tab (int x
) {
1926 if (x
>= 0 && x
< xw
.w
&& xw
.tabheight
> 0) {
1927 x
/= (xw
.w
/opt_tabcount
)+firstVisibleTab
;
1928 return (x
>= 0 && x
< term_count
) ? x
: -1;
1934 static void msTabSwitch (XEvent
*e
) {
1935 int tabn
= x2tab(e
->xbutton
.x
)+firstVisibleTab
;
1937 if (tabn
>= 0 && tabn
!= termidx
) switchToTerm(tabn
, 1);
1941 static void msTabScrollLeft (void) {
1942 if (firstVisibleTab
> 0) {
1950 static void msTabScrollRight (void) {
1951 int newidx
= firstVisibleTab
+1;
1953 if (newidx
> term_count
-opt_tabcount
) return;
1954 firstVisibleTab
= newidx
;
1960 static void bpress (XEvent
*e
) {
1961 if (term
== NULL
) return;
1963 if (xw
.tabheight
> 0) {
1964 if ((opt_tabposition
== 0 && e
->xbutton
.y
>= xw
.h
-xw
.tabheight
) ||
1965 (opt_tabposition
!= 0 && e
->xbutton
.y
< xw
.tabheight
)) {
1966 switch (e
->xbutton
.button
) {
1967 case Button1
: // left
1970 case Button4
: // wheel up
1973 case Button5
: // wheel down
1981 if ((e
->xbutton
.state
&ShiftMask
) != 0) {
1982 if (e
->xbutton
.button
== Button1
) {
1983 if (term
->sel
.bx
!= -1) tsetdirt(term
->sel
.b
.y
, term
->sel
.e
.y
);
1985 term
->sel
.b
.y
= term
->sel
.e
.y
= term
->row
+1;
1986 term
->sel
.ex
= term
->sel
.bx
= X2COL(e
->xbutton
.x
);
1987 term
->sel
.ey
= term
->sel
.by
= Y2ROW(e
->xbutton
.y
);
1988 //fprintf(stderr, "x=%d; y=%d\n", term->sel.bx, term->sel.by);
1993 if (e->xbutton.button == Button3) {
2001 if (IS_SET(MODE_MOUSE
)) mousereport(e
);
2005 static void brelease (XEvent
*e
) {
2006 if (term
== NULL
) return;
2008 switch (opt_tabposition
) {
2010 if (e
->xbutton
.y
>= xw
.h
-xw
.tabheight
) return;
2013 if (e
->xbutton
.y
< xw
.tabheight
) return;
2017 if ((e
->xbutton
.state
&ShiftMask
) == 0 && !term
->sel
.mode
) {
2018 if (IS_SET(MODE_MOUSE
)) mousereport(e
);
2022 if (e
->xbutton
.button
== Button2
) {
2023 selpaste(XA_PRIMARY
);
2024 } else if (e
->xbutton
.button
== Button1
) {
2026 getbuttoninfo(e
, NULL
, &term
->sel
.ex
, &term
->sel
.ey
); // this sets sel.b and sel.e
2028 if (term
->sel
.bx
== term
->sel
.ex
&& term
->sel
.by
== term
->sel
.ey
) {
2029 // single line, single char selection
2032 markDirty(term
->sel
.ey
, 2);
2034 now
= mclock_ticks();
2035 if (now
-term
->sel
.tclick2
<= opt_tripleclick_timeout
) {
2036 /* triple click on the line */
2037 term
->sel
.b
.x
= term
->sel
.bx
= 0;
2038 term
->sel
.e
.x
= term
->sel
.ex
= term
->col
;
2039 term
->sel
.b
.y
= term
->sel
.e
.y
= term
->sel
.ey
;
2040 } else if (now
-term
->sel
.tclick1
<= opt_doubleclick_timeout
) {
2041 /* double click to select word */
2042 Line l
= selgetlinebyy(term
->sel
.ey
);
2045 //FIXME: write better word selection code
2046 term
->sel
.bx
= term
->sel
.ex
;
2047 if (IS_GFX(l
[term
->sel
.bx
].attr
)) {
2048 while (term
->sel
.bx
> 0 && IS_GFX(l
[term
->sel
.bx
-1].attr
)) --term
->sel
.bx
;
2049 term
->sel
.b
.x
= term
->sel
.bx
;
2050 while (term
->sel
.ex
< term
->col
-1 && IS_GFX(l
[term
->sel
.ex
+1].attr
)) ++term
->sel
.ex
;
2052 while (term
->sel
.bx
> 0 && !IS_GFX(l
[term
->sel
.bx
-1].attr
) && l
[term
->sel
.bx
-1].c
[0] != ' ') --term
->sel
.bx
;
2053 term
->sel
.b
.x
= term
->sel
.bx
;
2054 while (term
->sel
.ex
< term
->col
-1 && !IS_GFX(l
[term
->sel
.ex
+1].attr
) && l
[term
->sel
.ex
+1].c
[0] != ' ') ++term
->sel
.ex
;
2056 term
->sel
.e
.x
= term
->sel
.ex
;
2057 term
->sel
.b
.y
= term
->sel
.e
.y
= term
->sel
.ey
;
2064 // multiline or multichar selection
2068 term
->sel
.tclick2
= term
->sel
.tclick1
;
2069 term
->sel
.tclick1
= mclock_ticks();
2074 static void bmotion (XEvent
*e
) {
2075 if (term
== NULL
) return;
2077 switch (opt_tabposition
) {
2079 if (e
->xbutton
.y
>= xw
.h
-xw
.tabheight
) return;
2082 if (e
->xbutton
.y
< xw
.tabheight
) return;
2086 if (term
->sel
.mode
) {
2087 int oldey
= term
->sel
.ey
, oldex
= term
->sel
.ex
;
2089 getbuttoninfo(e
, NULL
, &term
->sel
.ex
, &term
->sel
.ey
); // this sets sel.b and sel.e
2090 if (oldey
!= term
->sel
.ey
|| oldex
!= term
->sel
.ex
) {
2091 int starty
= MIN(oldey
, term
->sel
.ey
);
2092 int endy
= MAX(oldey
, term
->sel
.ey
);
2094 tsetdirt(starty
, endy
);
2099 //if (IS_SET(MODE_MOUSE) && e->xbutton.button != 0) mousereport(e);
2103 ////////////////////////////////////////////////////////////////////////////////
2106 static void dump (char c) {
2109 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
2110 if (++col % 10 == 0) fprintf(stderr, "\n");
2115 static __attribute__((noreturn
)) void execsh (const char *str
) {
2119 char *envshell
= getenv("SHELL");
2121 DEFAULT(envshell
, opt_shell
);
2122 setenv("TERM", opt_term
, 1);
2123 args
= opt_cmd
? opt_cmd
: (char *[]){envshell
, "-i", NULL
};
2127 args
= calloc(32768, sizeof(char *));
2128 if (args
== NULL
) exit(EXIT_FAILURE
);
2132 while (*str
&& isspace(*str
)) ++str
;
2136 while (*str
&& !isspace(*str
)) {
2137 if (*str
++ == '\\') {
2142 args
[argc
] = calloc(str
-b
+1, 1);
2143 memcpy(args
[argc
], b
, str
-b
);
2146 FILE *fo = fopen("z.log", "a");
2147 fprintf(fo, "%d: [%s]\n", argc, args[argc]);
2153 if (argc
< 1) exit(EXIT_FAILURE
);
2155 execvp(args
[0], args
);
2160 static int ttynew (Term
*term
) {
2162 struct winsize w
= {term
->row
, term
->col
, 0, 0};
2163 static int signalset
= 0;
2165 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0) die("openpty failed: %s", SERRNO
);
2169 switch (term
->pid
= fork()) {
2170 case -1: /* error */
2171 fprintf(stderr
, "fork failed");
2174 setsid(); /* create a new process group */
2175 dup2(s
, STDIN_FILENO
);
2176 dup2(s
, STDOUT_FILENO
);
2177 dup2(s
, STDERR_FILENO
);
2178 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0) die("ioctl TIOCSCTTY failed: %s", SERRNO
);
2181 execsh(term
->execcmd
);
2183 default: /* master */
2188 if (!signalset
) { signalset
= 1; signal(SIGCHLD
, sigchld
); }
2195 ////////////////////////////////////////////////////////////////////////////////
2197 static int ttycanread (void) {
2200 struct timeval timeout
= {0};
2202 if (term
->dead
|| term
->cmdfd
< 0) return 0;
2204 FD_SET(term
->cmdfd
, &rfd
);
2205 if (select(term
->cmdfd
+1, &rfd
, NULL
, NULL
, &timeout
) < 0) {
2206 if (errno
== EINTR
) continue;
2207 die("select failed: %s", SERRNO
);
2209 if (FD_ISSET(term
->cmdfd
, &rfd
)) return 1;
2216 static int ttycanwrite (void) {
2219 struct timeval timeout
= {0};
2221 if (term
->dead
|| term
->cmdfd
< 0) return 0;
2223 FD_SET(term
->cmdfd
, &wfd
);
2224 if (select(term
->cmdfd
+1, NULL
, &wfd
, NULL
, &timeout
) < 0) {
2225 if (errno
== EINTR
) continue;
2226 die("select failed: %s", SERRNO
);
2228 if (FD_ISSET(term
->cmdfd
, &wfd
)) return 1;
2236 static void wrstr (const char *s
, int len
) {
2237 if (s
== NULL
) return;
2239 unsigned char c
= (unsigned char)(*s
++);
2241 if (c
< 32) fprintf(stderr
, "{%u}", c
); else fwrite(&c
, 1, 1, stderr
);
2247 static void ttyread (void) {
2251 /* append read bytes to unprocessed bytes */
2252 if (term
== NULL
|| term
->dead
|| term
->cmdfd
< 0) return;
2253 #ifdef DUMP_PROG_OUTPUT
2254 term
->xobuflen
= term
->obuflen
;
2256 left
= OBUFSIZ
-term
->obuflen
;
2257 //dlogf("0: ttyread before: free=%d, used=%d", left, term->obuflen);
2258 while (left
> 0 && ttycanread()) {
2261 //if ((ret = read(term->cmdfd, term->obuf+term->obuflen, 1)) < 0) die("Couldn't read from shell: %s", SERRNO);
2262 if ((ret
= read(term
->cmdfd
, term
->obuf
+term
->obuflen
, left
)) < 0) {
2263 //fprintf(stderr, "Warning: couldn't read from tty #%d: %s\n", termidx, SERRNO);
2266 term
->obuflen
+= ret
;
2269 //dlogf("1: ttyread after: free=%d, used=%d", left, term->obuflen);
2270 /* process every complete utf8 char */
2271 #ifdef DUMP_PROG_OUTPUT
2273 FILE *fo
= fopen("zlogo.log", "ab");
2275 fwrite(term
->obuf
+term
->xobuflen
, term
->obuflen
-term
->xobuflen
, 1, fo
);
2281 if (term
->needConv
) {
2282 // need conversion from locale to utf-8
2283 //fprintf(stderr, "buf: %d bytes\n", term->obuflen);
2284 while (term
->obuflen
> 0) {
2285 char obuf
[UTF_SIZ
+1];
2288 len
= loc2utf(obuf
, ptr
, 1);
2291 fprintf(stderr
, "rdc: [");
2293 fprintf(stderr
, "] --> [");
2295 fprintf(stderr
, "]\n");
2308 // don't do any conversion
2309 while (term
->obuflen
>= UTF_SIZ
|| isfullutf8(ptr
, term
->obuflen
)) {
2312 int charsize
= utf8decode(&utf8c
, ptr
);
2315 len
= utf8encode(s
, utf8c
);
2318 fprintf(stderr
, "rdx: [");
2320 fprintf(stderr
, "]\n");
2329 term
->obuflen
-= charsize
;
2331 //dlogf("2: ttyread afterproc: used=%d", term->obuflen);
2333 /* keep any uncomplete utf8 char for the next call */
2334 if (term
->obuflen
> 0) memmove(term
->obuf
, ptr
, term
->obuflen
);
2338 static void ttyflushwrbuf (void) {
2339 if (term
== NULL
|| term
->dead
|| term
->cmdfd
< 0) return;
2340 if (term
->wrbufpos
>= term
->wrbufused
) {
2341 term
->wrbufpos
= term
->wrbufused
= 0;
2344 //dlogf("0: ttyflushwrbuf before: towrite=%d", term->wrbufused-term->wrbufpos);
2345 while (term
->wrbufpos
< term
->wrbufused
&& ttycanwrite()) {
2348 if ((ret
= write(term
->cmdfd
, term
->wrbuf
+term
->wrbufpos
, term
->wrbufused
-term
->wrbufpos
)) == -1) {
2349 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2351 term
->wrbufpos
+= ret
;
2353 if (term
->wrbufpos
> 0) {
2354 int left
= term
->wrbufused
-term
->wrbufpos
;
2357 // write buffer is empty
2358 term
->wrbufpos
= term
->wrbufused
= 0;
2360 memmove(term
->wrbuf
, term
->wrbuf
+term
->wrbufpos
, left
);
2362 term
->wrbufused
= left
;
2365 //dlogf("1: ttyflushwrbuf after: towrite=%d", term->wrbufused-term->wrbufpos);
2369 // convert char to locale and write it
2370 static void ttywriterawchar (const char *s
, int len
, int noenc
) {
2374 if (s
== NULL
|| len
< 1) return;
2376 if (term
->needConv
) {
2377 if ((clen
= utf2loc(loc
, s
, len
)) < 1) return;
2379 if ((clen
= utf8size(s
)) < 1) return;
2380 memmove(loc
, s
, clen
);
2383 memmove(loc
, s
, (clen
= len
));
2385 #ifdef DUMP_IO_WRITE
2387 fprintf(stderr
, "wrc: [");
2389 fprintf(stderr
, "] --> [");
2391 fprintf(stderr
, "]\n");
2396 while (term
->wrbufused
+clen
>= term
->wrbufsize
) {
2397 //FIXME: make write buffer dynamic?
2398 // force write at least one char
2399 //dlogf("ttywrite: forced write");
2400 if (write(term
->cmdfd
, term
->wrbuf
+term
->wrbufpos
, 1) == -1) {
2401 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2405 ttyflushwrbuf(); // make room for char
2407 memcpy(term
->wrbuf
+term
->wrbufused
, loc
, clen
);
2408 term
->wrbufused
+= clen
;
2412 static void ttywritenoenc (const char *s
, size_t n
) {
2414 term
->ubufpos
= 0; // discard possible utf-8 char
2415 while (n
-- > 0) ttywriterawchar(s
++, 1, 1);
2421 static void ttywrite (const char *s
, size_t n
) {
2422 if (term
== NULL
|| term
->dead
|| term
->cmdfd
< 0) return;
2423 #ifdef DUMP_PROG_INPUT
2424 if (s
!= NULL
&& n
> 0) {
2425 FILE *fo
= fopen("zlogw.log", "ab");
2427 fwrite(s
, n
, 1, fo
);
2433 if (s
!= NULL
&& n
> 0) {
2435 unsigned char c
= (unsigned char)(s
[0]);
2437 if (term
->ubufpos
> 0 && isfullutf8(term
->ubuf
, term
->ubufpos
)) {
2438 // have complete char
2439 ttywriterawchar(term
->ubuf
, term
->ubufpos
, 0);
2444 if (term
->ubufpos
== 0) {
2447 ttywriterawchar(s
, 1, 0);
2448 } else if ((c
&0xc0) == 0xc0) {
2450 term
->ubuf
[term
->ubufpos
++] = *s
;
2452 // ignore unsynced utf-8
2459 if (c
< 128 || term
->ubufpos
>= UTF_SIZ
|| (c
&0xc0) == 0xc0) {
2460 // discard previous utf-8, it's bad
2465 term
->ubuf
[term
->ubufpos
++] = *s
;
2468 if (isfullutf8(term
->ubuf
, term
->ubufpos
)) {
2469 // have complete char
2470 ttywriterawchar(term
->ubuf
, term
->ubufpos
, 0);
2479 ////////////////////////////////////////////////////////////////////////////////
2481 static void ttyresize (void) {
2484 if (term
!= NULL
&& term
->cmdfd
>= 0) {
2485 w
.ws_row
= term
->row
;
2486 w
.ws_col
= term
->col
;
2487 w
.ws_xpixel
= w
.ws_ypixel
= 0;
2488 if (ioctl(term
->cmdfd
, TIOCSWINSZ
, &w
) < 0) fprintf(stderr
, "Warning: couldn't set window size: %s\n", SERRNO
);
2494 ////////////////////////////////////////////////////////////////////////////////
2496 static void csidump (void) {
2498 for (int f
= 1; f
< term
->escseq
.len
; ++f
) {
2499 uint c
= (term
->escseq
.buf
[f
]&0xff);
2501 if (isprint(c
)) putchar(c
);
2502 else if (c
== '\n') printf("(\\n)");
2503 else if (c
== '\r') printf("(\\r)");
2504 else if (c
== 0x1b) printf("(\\e)");
2505 else printf("(%02x)", c
);
2511 static void tsetdirt (int top
, int bot
) {
2512 LIMIT(top
, 0, term
->row
-1);
2513 LIMIT(bot
, 0, term
->row
-1);
2514 for (int y
= top
; y
<= bot
; ++y
) markDirty(y
, 2);
2518 static void tfulldirt (void) {
2519 tsetdirt(0, term
->row
-1);
2523 static void tmoveto (int x
, int y
) {
2524 LIMIT(x
, 0, term
->col
-1);
2525 LIMIT(y
, 0, term
->row
-1);
2526 term
->c
.state
&= ~CURSOR_WRAPNEXT
;
2527 if (term
->c
.x
!= x
|| term
->c
.y
!= y
) {
2535 static void tclearregion (int x1
, int y1
, int x2
, int y2
) {
2538 //fprintf(stderr, "tclearregion: (%d,%d)-(%d,%d)\n", x1, y1, x2, y2);
2539 if (x1
> x2
) { temp
= x1
; x1
= x2
; x2
= temp
; }
2540 if (y1
> y2
) { temp
= y1
; y1
= y2
; y2
= temp
; }
2541 LIMIT(x1
, 0, term
->col
-1);
2542 LIMIT(x2
, 0, term
->col
-1);
2543 LIMIT(y1
, 0, term
->row
-1);
2544 LIMIT(y2
, 0, term
->row
-1);
2545 //fprintf(stderr, " tclearregion: (%d,%d)-(%d,%d)\n", x1, y1, x2, y2);
2546 for (int y
= y1
; y
<= y2
; ++y
) {
2547 Line l
= term
->line
[y
];
2549 markDirty(y
, (x1
<= 0 && x2
>= term
->col
-1) ? 2 : 1);
2550 for (int x
= x1
; x
<= x2
; ++x
) {
2551 l
[x
].fg
= term
->c
.attr
.fg
;
2552 l
[x
].bg
= term
->c
.attr
.bg
;
2553 l
[x
].state
= GLYPH_DIRTY
;
2554 l
[x
].attr
= ATTR_NULL
|(term
->c
.attr
.attr
&(ATTR_DEFFG
|ATTR_DEFBG
));
2556 if (term
->sel
.bx
!= -1 && selected(x
, y
)) selhide();
2558 l
[term
->col
-1].state
&= ~GLYPH_WRAP
;
2563 static void tcursor (int mode
) {
2564 if (mode
== CURSOR_SAVE
) {
2565 term
->csaved
= term
->c
;
2566 } else if (mode
== CURSOR_LOAD
) {
2567 term
->c
= term
->csaved
;
2568 tmoveto(term
->c
.x
, term
->c
.y
);
2574 static void treset (void) {
2577 term
->c
= (TCursor
){{
2578 .attr
= ATTR_NULL
|ATTR_DEFFG
|ATTR_DEFBG
,
2581 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
2582 term
->c
.attr
.fg
= term
->deffg
;
2583 term
->c
.attr
.bg
= term
->defbg
;
2585 g
.state
= GLYPH_DIRTY
;
2586 g
.attr
= term
->c
.attr
.attr
;
2587 g
.fg
= term
->c
.attr
.fg
;
2588 g
.bg
= term
->c
.attr
.bg
;
2593 term
->bot
= term
->row
-1;
2594 term
->mode
= MODE_WRAP
/* | MODE_MOUSEBTN*/ | MODE_GFX1
;
2595 term
->mousemode
= 1000;
2596 term
->charset
= MODE_GFX0
;
2597 //tclearregion(0, 0, term->col-1, term->row-1);
2598 for (int y
= 0; y
< term
->row
; ++y
) {
2600 for (int x
= 0; x
< term
->col
; ++x
) term
->alt
[y
][x
] = term
->line
[y
][x
] = g
;
2602 for (int y
= term
->row
; y
< term
->linecount
; ++y
) {
2603 for (int x
= 0; x
< term
->col
; ++x
) term
->line
[y
][x
] = g
;
2605 tcursor(CURSOR_SAVE
);
2611 static int tinitialize (int col
, int row
) {
2612 //memset(term, 0, sizeof(Term));
2613 //term->needConv = needConversion ? 1 : 0;
2614 term
->wrbufsize
= WBUFSIZ
;
2615 term
->deffg
= term
->deffg
;
2616 term
->defbg
= term
->defbg
;
2619 term
->dirty
= calloc(term
->row
, sizeof(*term
->dirty
));
2620 term
->maxhistory
= opt_maxhistory
;
2621 term
->linecount
= term
->maxhistory
+term
->row
;
2622 term
->line
= calloc(term
->linecount
, sizeof(Line
));
2623 term
->alt
= calloc(term
->row
, sizeof(Line
));
2624 for (int y
= 0; y
< term
->linecount
; ++y
) term
->line
[y
] = calloc(term
->col
, sizeof(Glyph
));
2625 for (int y
= 0; y
< term
->row
; ++y
) term
->alt
[y
] = calloc(term
->col
, sizeof(Glyph
));
2632 static void tadjustmaxhistory (int maxh
) {
2634 LIMIT(maxh
, 0, 65535);
2635 if (term
->maxhistory
< maxh
) {
2637 int newlc
= term
->linecount
+(maxh
-term
->maxhistory
);
2639 // add history lines
2640 if ((nl
= realloc(term
->line
, sizeof(Line
)*newlc
)) != NULL
) {
2645 g
.state
= GLYPH_DIRTY
;
2646 g
.attr
= ATTR_NULL
|ATTR_DEFFG
|ATTR_DEFBG
;
2651 for (int y
= term
->linecount
; y
< newlc
; ++y
) {
2652 term
->line
[y
] = calloc(term
->col
, sizeof(Glyph
));
2653 for (int x
= 0; x
< term
->col
; ++x
) term
->line
[y
][x
] = g
;
2655 term
->maxhistory
= maxh
;
2656 term
->linecount
= newlc
;
2658 } else if (term
->maxhistory
> maxh
) {
2662 // remove history lines
2663 while (term
->linecount
> term
->row
+maxh
) free(term
->line
[--term
->linecount
]);
2664 if ((nl
= realloc(term
->line
, sizeof(Line
)*term
->linecount
)) != NULL
) term
->line
= nl
;
2665 term
->maxhistory
= maxh
;
2671 static void tswapscreen (void) {
2673 for (int f
= 0; f
< term
->row
; ++f
) {
2674 Line t
= term
->line
[f
];
2676 term
->line
[f
] = term
->alt
[f
];
2679 term
->mode
^= MODE_ALTSCREEN
;
2684 //FIXME: works bad with history
2686 static void selscroll (int orig
, int n
, int tohistory
) {
2689 if (term
->sel
.bx
== -1) return;
2691 tfulldirt(); // just in case
2693 if (BETWEEN(term
->sel
.by
, orig
, term
->bot
) || BETWEEN(term
->sel
.ey
, orig
, term
->bot
)) {
2694 if ((term
->sel
.by
+= n
) > term
->bot
|| (term
->sel
.ey
+= n
) < term
->top
) {
2698 if (term
->sel
.by
< term
->top
) {
2699 term
->sel
.by
= term
->top
;
2703 if (term
->sel
.ey
> term
->bot
) {
2704 term
->sel
.ey
= term
->bot
;
2705 term
->sel
.ex
= term
->col
;
2708 term
->sel
.b
.x
= term
->sel
.bx
;
2709 term
->sel
.b
.y
= term
->sel
.by
;
2710 term
->sel
.e
.x
= term
->sel
.ex
;
2711 term
->sel
.e
.y
= term
->sel
.ey
;
2714 // tohistory!=0; always scrolls full screen up (n == -1)
2715 //fprintf(stderr, "selscroll to history\n");
2718 //fprintf(stderr, " by=%d; ey=%d; maxhistory=%d\n", term->sel.by, term->sel.ey, term->maxhistory);
2719 if (term
->sel
.ey
< 0 && -(term
->sel
.ey
) > term
->maxhistory
) {
2720 // out of screen completely
2724 if (term
->sel
.by
< 0 && -(term
->sel
.by
) > term
->maxhistory
) {
2725 term
->sel
.by
= -term
->maxhistory
;
2729 term
->sel
.b
.x
= term
->sel
.bx
;
2730 term
->sel
.b
.y
= term
->sel
.by
;
2731 term
->sel
.e
.x
= term
->sel
.ex
;
2732 term
->sel
.e
.y
= term
->sel
.ey
;
2735 if (docopy
) selcopy();
2739 static void tscrolldown (int orig
, int n
) {
2742 LIMIT(n
, 0, term
->bot
-orig
+1);
2744 selscroll(orig
, n
, 0);
2745 //fprintf(stderr, "tscrolldown(%d, %d)\n", orig, n);
2746 tclearregion(0, term
->bot
-n
+1, term
->col
-1, term
->bot
);
2747 for (int f
= term
->bot
; f
>= orig
+n
; --f
) {
2748 temp
= term
->line
[f
];
2749 term
->line
[f
] = term
->line
[f
-n
];
2750 term
->line
[f
-n
] = temp
;
2757 static void tscrollup (int orig
, int n
, int tohistory
) {
2760 if (term
== NULL
) return;
2761 LIMIT(n
, 0, term
->bot
-orig
+1);
2763 //fprintf(stderr, "tscrollup(%d, %d)\n", orig, n);
2764 if (tohistory
&& !IS_SET(MODE_ALTSCREEN
) && term
->maxhistory
> 0) {
2765 Line l
= term
->line
[term
->linecount
-1];
2767 for (int f
= term
->linecount
-1; f
> term
->row
; --f
) term
->line
[f
] = term
->line
[f
-1];
2768 term
->line
[term
->row
] = l
;
2769 for (int x
= 0; x
< term
->col
; ++x
) l
[x
] = term
->line
[0][x
];
2774 selscroll(orig
, -n
, tohistory
);
2775 //tclearregion(0, orig, term->col-1, orig+n-1);
2776 for (int f
= orig
; f
<= term
->bot
-n
; ++f
) {
2777 temp
= term
->line
[f
];
2778 term
->line
[f
] = term
->line
[f
+n
];
2779 term
->line
[f
+n
] = temp
;
2783 tclearregion(0, term
->bot
-n
+1, term
->col
-1, term
->bot
);
2787 static inline void tsetcharwrap (int y
, int wrap
) {
2788 if (y
>= 0 && y
< term
->row
) {
2789 if (wrap
) term
->line
[y
][term
->col
-1].state
|= GLYPH_WRAP
;
2790 else term
->line
[y
][term
->col
-1].state
&= ~GLYPH_WRAP
;
2795 static void tnewline (int first_col
) {
2798 tsetcharwrap(y
, (first_col
== 2)); // 2: wrapping
2799 if (y
== term
->bot
) tscrollup(term
->top
, 1, 1); else ++y
;
2800 tmoveto(first_col
? 0 : term
->c
.x
, y
);
2804 static void csiparse (void) {
2805 const char *p
= term
->escseq
.buf
;
2807 term
->escseq
.narg
= 0;
2808 if (*p
== '?') { term
->escseq
.priv
= 1; ++p
; }
2809 while (p
< term
->escseq
.buf
+term
->escseq
.len
) {
2810 int n
= term
->escseq
.arg
[term
->escseq
.narg
];
2812 for (; *p
&& isdigit(*p
); ++p
) n
= n
*10+(p
[0]-'0');
2813 term
->escseq
.arg
[term
->escseq
.narg
] = n
;
2815 if (*p
== ';' && term
->escseq
.narg
+1 < ESC_ARG_SIZ
) {
2816 ++term
->escseq
.narg
;
2819 term
->escseq
.mode
= *p
;
2820 ++term
->escseq
.narg
;
2827 static void tsetchar (const char *c
) {
2829 int rev
= 0, gfx
= 0;
2830 int x
= term
->c
.x
, y
= term
->c
.y
;
2832 if (x
< 0 || x
>= term
->col
|| y
< 0 || y
>= term
->row
) return;
2834 if (!term
->needConv
&& unimap
!= NULL
&& (unsigned char)c
[0] >= 0x80) {
2839 ushort uc
= unimap
[cc
];
2841 //fprintf(stderr, "unimap: %d [%02X] 0x%04x -> 0x%04x\n", (unsigned char)c[0], cc, uc);
2861 term
->line
[y
][x
] = term
->c
.attr
;
2862 if (rev
) term
->line
[y
][x
].attr
^= ATTR_REVERSE
;
2863 if (gfx
|| (term
->mode
&term
->charset
)) {
2864 term
->line
[y
][x
].attr
|= ATTR_GFX
;
2866 term
->line
[y
][x
].attr
&= ~ATTR_GFX
;
2869 term
->line
[y
][x
].state
= (GLYPH_SET
| GLYPH_DIRTY
);
2870 memcpy(term
->line
[y
][x
].c
, c
, UTF_SIZ
);
2872 if (IS_GFX(term
->line
[y
][x
].attr
)) {
2873 unsigned char c
= (unsigned char)(term
->line
[y
][x
].c
[0]);
2875 if (c
> 95 && c
< 128) term
->line
[y
][x
].c
[0] -= 95;
2876 else if (c
> 127) term
->line
[y
][x
].c
[0] = ' ';
2878 if (term
->sel
.bx
!= -1 && selected(x
, y
)) selhide();
2879 //dlogf("tsetchar(%d,%d): [%c] (%d); dirty=%d\n", x, y, c[0], term->wantRedraw, term->dirty[y]);
2883 static void tdeletechar (int n
) {
2884 int src
= term
->c
.x
+n
;
2885 int dst
= term
->c
.x
;
2886 int size
= term
->col
-src
;
2888 markDirty(term
->c
.y
, 2);
2889 if (src
>= term
->col
) {
2890 tclearregion(term
->c
.x
, term
->c
.y
, term
->col
-1, term
->c
.y
);
2892 memmove(&term
->line
[term
->c
.y
][dst
], &term
->line
[term
->c
.y
][src
], size
*sizeof(Glyph
));
2893 tclearregion(term
->col
-n
, term
->c
.y
, term
->col
-1, term
->c
.y
);
2898 static void tinsertblank (int n
) {
2899 int src
= term
->c
.x
;
2901 int size
= term
->col
-dst
;
2903 markDirty(term
->c
.y
, 2);
2904 if (dst
>= term
->col
) {
2905 tclearregion(term
->c
.x
, term
->c
.y
, term
->col
-1, term
->c
.y
);
2907 memmove(&term
->line
[term
->c
.y
][dst
], &term
->line
[term
->c
.y
][src
], size
*sizeof(Glyph
));
2908 tclearregion(src
, term
->c
.y
, dst
-1, term
->c
.y
);
2913 static void tinsertblankline (int n
) {
2914 if (term
->c
.y
< term
->top
|| term
->c
.y
> term
->bot
) return;
2915 tscrolldown(term
->c
.y
, n
);
2919 static void tdeleteline (int n
) {
2920 if (term
->c
.y
< term
->top
|| term
->c
.y
> term
->bot
) return;
2921 tscrollup(term
->c
.y
, n
, 0);
2925 static void tsetattr (int *attr
, int l
) {
2926 for (int f
= 0; f
< l
; ++f
) {
2929 term
->c
.attr
.attr
&= ~(ATTR_REVERSE
| ATTR_UNDERLINE
| ATTR_BOLD
);
2930 term
->c
.attr
.attr
|= ATTR_DEFFG
| ATTR_DEFBG
;
2931 term
->c
.attr
.fg
= term
->deffg
;
2932 term
->c
.attr
.bg
= term
->defbg
;
2935 term
->c
.attr
.attr
|= ATTR_BOLD
;
2938 term
->c
.attr
.attr
|= ATTR_UNDERLINE
;
2941 term
->c
.attr
.attr
|= ATTR_REVERSE
;
2944 term
->c
.attr
.attr
&= ~ATTR_BOLD
;
2947 term
->c
.attr
.attr
&= ~ATTR_UNDERLINE
;
2950 term
->c
.attr
.attr
&= ~ATTR_REVERSE
;
2953 if (f
+2 < l
&& attr
[f
+1] == 5) {
2955 if (BETWEEN(attr
[f
], 0, 255)) {
2956 term
->c
.attr
.fg
= attr
[f
];
2957 term
->c
.attr
.attr
&= ~ATTR_DEFFG
;
2959 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[f
]);
2962 //fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2963 term
->c
.attr
.fg
= term
->deffg
;
2964 term
->c
.attr
.attr
|= ATTR_DEFFG
;
2968 term
->c
.attr
.fg
= term
->deffg
;
2969 term
->c
.attr
.attr
|= ATTR_DEFFG
;
2972 if (f
+2 < l
&& attr
[f
+1] == 5) {
2974 if (BETWEEN(attr
[f
], 0, 255)) {
2975 term
->c
.attr
.bg
= attr
[f
];
2976 term
->c
.attr
.attr
&= ~ATTR_DEFBG
;
2978 fprintf(stderr
, "erresc: bad bgcolor %d\n", attr
[f
]);
2981 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[f
]);
2985 term
->c
.attr
.bg
= term
->defbg
;
2986 term
->c
.attr
.attr
|= ATTR_DEFBG
;
2989 if (BETWEEN(attr
[f
], 30, 37)) { term
->c
.attr
.fg
= attr
[f
]-30; term
->c
.attr
.attr
&= ~ATTR_DEFFG
; }
2990 else if (BETWEEN(attr
[f
], 40, 47)) { term
->c
.attr
.bg
= attr
[f
]-40; term
->c
.attr
.attr
&= ~ATTR_DEFBG
; }
2991 else if (BETWEEN(attr
[f
], 90, 97)) { term
->c
.attr
.fg
= attr
[f
]-90+8; term
->c
.attr
.attr
&= ~ATTR_DEFFG
; }
2992 else if (BETWEEN(attr
[f
], 100, 107)) { term
->c
.attr
.bg
= attr
[f
]-100+8; term
->c
.attr
.attr
&= ~ATTR_DEFBG
; }
2993 else { fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[f
]); csidump(); }
3000 static void tsetscroll (int t
, int b
) {
3003 LIMIT(t
, 0, term
->row
-1);
3004 LIMIT(b
, 0, term
->row
-1);
3015 ////////////////////////////////////////////////////////////////////////////////
3017 static void csihandle (void) {
3018 switch (term
->escseq
.mode
) {
3019 case '@': /* ICH -- Insert <n> blank char */
3020 DEFAULT(term
->escseq
.arg
[0], 1);
3021 tinsertblank(term
->escseq
.arg
[0]);
3023 case 'A': /* CUU -- Cursor <n> Up */
3025 DEFAULT(term
->escseq
.arg
[0], 1);
3026 tmoveto(term
->c
.x
, term
->c
.y
-term
->escseq
.arg
[0]);
3028 case 'B': /* CUD -- Cursor <n> Down */
3029 DEFAULT(term
->escseq
.arg
[0], 1);
3030 tmoveto(term
->c
.x
, term
->c
.y
+term
->escseq
.arg
[0]);
3032 case 'C': /* CUF -- Cursor <n> Forward */
3034 DEFAULT(term
->escseq
.arg
[0], 1);
3035 tmoveto(term
->c
.x
+term
->escseq
.arg
[0], term
->c
.y
);
3037 case 'D': /* CUB -- Cursor <n> Backward */
3038 DEFAULT(term
->escseq
.arg
[0], 1);
3039 tmoveto(term
->c
.x
-term
->escseq
.arg
[0], term
->c
.y
);
3041 case 'E': /* CNL -- Cursor <n> Down and first col */
3042 DEFAULT(term
->escseq
.arg
[0], 1);
3043 tmoveto(0, term
->c
.y
+term
->escseq
.arg
[0]);
3045 case 'F': /* CPL -- Cursor <n> Up and first col */
3046 DEFAULT(term
->escseq
.arg
[0], 1);
3047 tmoveto(0, term
->c
.y
-term
->escseq
.arg
[0]);
3049 case 'G': /* CHA -- Move to <col> */
3050 case '`': /* XXX: HPA -- same? */
3051 DEFAULT(term
->escseq
.arg
[0], 1);
3052 tmoveto(term
->escseq
.arg
[0]-1, term
->c
.y
);
3054 case 'H': /* CUP -- Move to <row> <col> */
3055 case 'f': /* XXX: HVP -- same? */
3056 DEFAULT(term
->escseq
.arg
[0], 1);
3057 DEFAULT(term
->escseq
.arg
[1], 1);
3058 tmoveto(term
->escseq
.arg
[1]-1, term
->escseq
.arg
[0]-1);
3060 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
3061 case 'J': /* ED -- Clear screen */
3063 switch (term
->escseq
.arg
[0]) {
3065 tclearregion(term
->c
.x
, term
->c
.y
, term
->col
-1, term
->c
.y
);
3066 if (term
->c
.y
< term
->row
-1) tclearregion(0, term
->c
.y
+1, term
->col
-1, term
->row
-1);
3069 if (term
->c
.y
> 1) tclearregion(0, 0, term
->col
-1, term
->c
.y
-1);
3070 tclearregion(0, term
->c
.y
, term
->c
.x
, term
->c
.y
);
3073 tclearregion(0, 0, term
->col
-1, term
->row
-1);
3079 case 'K': /* EL -- Clear line */
3080 switch (term
->escseq
.arg
[0]) {
3082 tclearregion(term
->c
.x
, term
->c
.y
, term
->col
-1, term
->c
.y
);
3085 tclearregion(0, term
->c
.y
, term
->c
.x
, term
->c
.y
);
3088 tclearregion(0, term
->c
.y
, term
->col
-1, term
->c
.y
);
3092 case 'S': /* SU -- Scroll <n> line up */
3093 DEFAULT(term
->escseq
.arg
[0], 1);
3094 tscrollup(term
->top
, term
->escseq
.arg
[0], 0);
3096 case 'T': /* SD -- Scroll <n> line down */
3097 DEFAULT(term
->escseq
.arg
[0], 1);
3098 tscrolldown(term
->top
, term
->escseq
.arg
[0]);
3100 case 'L': /* IL -- Insert <n> blank lines */
3101 DEFAULT(term
->escseq
.arg
[0], 1);
3102 tinsertblankline(term
->escseq
.arg
[0]);
3104 case 'l': /* RM -- Reset Mode */
3105 if (term
->escseq
.priv
) {
3106 switch (term
->escseq
.arg
[0]) {
3107 case 1: // 1001 for xterm compatibility
3108 DUMP_KEYPAD_SWITCH("1", "OFF");
3109 term
->mode
&= ~MODE_APPKEYPAD
;
3111 case 5: /* DECSCNM -- Remove reverse video */
3112 if (IS_SET(MODE_REVERSE
)) {
3113 term
->mode
&= ~MODE_REVERSE
;
3117 case 7: /* autowrap off */
3118 term
->mode
&= ~MODE_WRAP
;
3120 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
3122 case 20: /* non-standard code? */
3123 term
->mode
&= ~MODE_CRLF
;
3125 case 25: /* hide cursor */
3126 if ((term
->c
.state
&CURSOR_HIDE
) == 0) {
3127 term
->c
.state
|= CURSOR_HIDE
;
3128 term
->wantRedraw
= 1;
3131 case 1000: /* disable X11 xterm mouse reporting */
3132 term
->mode
&= ~MODE_MOUSEBTN
;
3135 term
->mode
&= ~MODE_MOUSEMOTION
;
3138 term
->mode
&= ~MODE_FOCUSEVT
;
3140 case 1005: /* utf-8 mouse encoding */
3141 case 1006: /* sgr mouse encoding */
3142 case 1015: /* urxvt mouse encoding */
3143 term
->mousemode
= 1000;
3145 case 1049: /* = 1047 and 1048 */
3148 if (IS_SET(MODE_ALTSCREEN
)) {
3149 tclearregion(0, 0, term
->col
-1, term
->row
-1);
3152 if (term
->escseq
.arg
[0] != 1049) break;
3154 tcursor(CURSOR_LOAD
);
3156 case 2004: /* reset bracketed paste mode */
3157 term
->mode
&= ~MODE_BRACPASTE
;
3163 switch (term
->escseq
.arg
[0]) {
3165 term
->mode
&= ~MODE_DISPCTRL
;
3168 term
->mode
&= ~MODE_INSERT
;
3175 case 'M': /* DL -- Delete <n> lines */
3176 DEFAULT(term
->escseq
.arg
[0], 1);
3177 tdeleteline(term
->escseq
.arg
[0]);
3179 case 'X': /* ECH -- Erase <n> char */
3180 DEFAULT(term
->escseq
.arg
[0], 1);
3181 tclearregion(term
->c
.x
, term
->c
.y
, term
->c
.x
+ term
->escseq
.arg
[0], term
->c
.y
);
3183 case 'P': /* DCH -- Delete <n> char */
3184 DEFAULT(term
->escseq
.arg
[0], 1);
3185 tdeletechar(term
->escseq
.arg
[0]);
3187 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
3188 case 'd': /* VPA -- Move to <row> */
3189 DEFAULT(term
->escseq
.arg
[0], 1);
3190 tmoveto(term
->c
.x
, term
->escseq
.arg
[0]-1);
3192 case 'h': /* SM -- Set terminal mode */
3193 if (term
->escseq
.priv
) {
3194 switch (term
->escseq
.arg
[0]) {
3196 DUMP_KEYPAD_SWITCH("1", "ON");
3197 term
->mode
|= MODE_APPKEYPAD
;
3199 case 5: /* DECSCNM -- Reverve video */
3200 if (!IS_SET(MODE_REVERSE
)) {
3201 term
->mode
|= MODE_REVERSE
;
3206 term
->mode
|= MODE_WRAP
;
3209 term
->mode
|= MODE_CRLF
;
3211 case 12: /* att610 -- Start blinking cursor (IGNORED) */
3212 /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
3213 if (term
->escseq
.narg
> 1 && term
->escseq
.arg
[1] != 25) break;
3215 if ((term
->c
.state
&CURSOR_HIDE
) != 0) {
3216 term
->c
.state
&= ~CURSOR_HIDE
;
3217 term
->wantRedraw
= 1;
3220 case 1000: /* 1000,1002: enable xterm mouse report */
3221 term
->mode
|= MODE_MOUSEBTN
;
3224 term
->mode
|= MODE_MOUSEMOTION
;
3227 term
->mode
|= MODE_FOCUSEVT
;
3229 case 1005: /* utf-8 mouse encoding */
3230 case 1006: /* sgr mouse encoding */
3231 case 1015: /* urxvt mouse encoding */
3232 term
->mousemode
= term
->escseq
.arg
[0];
3234 case 1049: /* = 1047 and 1048 */
3237 if (IS_SET(MODE_ALTSCREEN
)) tclearregion(0, 0, term
->col
-1, term
->row
-1); else tswapscreen();
3238 if (term
->escseq
.arg
[0] != 1049) break;
3240 tcursor(CURSOR_SAVE
);
3242 case 2004: /* set bracketed paste mode */
3243 term
->mode
|= MODE_BRACPASTE
;
3245 default: goto unknown
;
3248 switch (term
->escseq
.arg
[0]) {
3250 term
->mode
|= MODE_DISPCTRL
;
3253 term
->mode
|= MODE_INSERT
;
3260 case 'm': /* SGR -- Terminal attribute (color) */
3261 tsetattr(term
->escseq
.arg
, term
->escseq
.narg
);
3264 if (!term
->escseq
.priv
) {
3265 switch (term
->escseq
.arg
[0]) {
3266 case 5: /* Device status report (DSR) */
3267 ttywritestr("\x1b[0n");
3269 case 6: { /* cursor position report */
3272 sprintf(buf
, "\x1b[%d;%dR", term
->c
.x
+1, term
->c
.y
+1);
3278 case 'r': /* DECSTBM -- Set Scrolling Region */
3279 if (term
->escseq
.priv
&& term
->escseq
.arg
[0] == 1001) {
3280 // xterm compatibility
3281 DUMP_KEYPAD_SWITCH("1001", "OFF");
3282 term
->mode
&= ~MODE_APPKEYPAD
;
3283 } else if (term
->escseq
.priv
) {
3286 DEFAULT(term
->escseq
.arg
[0], 1);
3287 DEFAULT(term
->escseq
.arg
[1], term
->row
);
3288 tsetscroll(term
->escseq
.arg
[0]-1, term
->escseq
.arg
[1]-1);
3292 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
3293 if (term
->escseq
.priv
&& term
->escseq
.arg
[0] == 1001) {
3294 // xterm compatibility
3295 DUMP_KEYPAD_SWITCH("1001", "ON");
3296 term
->mode
|= MODE_APPKEYPAD
;
3298 tcursor(CURSOR_SAVE
);
3301 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
3302 tcursor(CURSOR_LOAD
);
3306 fprintf(stderr
, "erresc: unknown csi ");
3313 static void csireset (void) {
3314 memset(&term
->escseq
, 0, sizeof(term
->escseq
));
3318 static void tputtab (void) {
3319 int space
= opt_tabsize
-term
->c
.x
%opt_tabsize
;
3321 if (space
> 0) tmoveto(term
->c
.x
+space
, term
->c
.y
);
3325 ////////////////////////////////////////////////////////////////////////////////
3326 // put char to output buffer or process command
3328 // return 1 if this was control character
3329 // return -1 if this should break esape sequence
3330 static int tputc_ctrl (char ascii
) {
3333 if (term
->esc
&ESC_TITLE
) return 0;
3336 case '\t': tputtab(); break;
3337 case '\b': tmoveto(term
->c
.x
-1, term
->c
.y
); break;
3338 case '\r': tmoveto(0, term
->c
.y
); break;
3339 case '\f': case '\n': case '\v': tnewline(IS_SET(MODE_CRLF
)?1:0); break; /* go to first col if the mode is set */
3341 if (!(xw
.state
& WIN_FOCUSED
) && (term
->belltype
&BELL_URGENT
)) xseturgency(1);
3342 if (term
->belltype
&BELL_AUDIO
) XBell(xw
.dpy
, 100);
3344 case 14: term
->charset
= MODE_GFX1
; break;
3345 case 15: term
->charset
= MODE_GFX0
; break;
3346 case 0x18: case 0x1a: res
= -1; break; // do nothing, interrupt current escape sequence
3347 case 127: break; // ignore it
3348 case '\033': csireset(); term
->esc
= ESC_START
; break;
3349 //case 0x9b: csireset(); term->esc = ESC_START | ESC_CSI; break;
3350 default: res
= 0; break;
3356 static void tputc (const char *c
) {
3358 int ctl
= tputc_ctrl(ascii
);
3360 if (ctl
> 0) return; // control char; should not break escape sequence
3362 // control char; should break escape sequence
3366 //dlogf("tputc: [%c]\n", c[0]);
3367 if (term
->esc
& ESC_START
) {
3368 if (term
->esc
& ESC_CSI
) {
3369 term
->escseq
.buf
[term
->escseq
.len
++] = ascii
;
3370 if (BETWEEN(ascii
, 0x40, 0x7E) || term
->escseq
.len
>= ESC_BUF_SIZ
) {
3375 } else if (term
->esc
& ESC_OSC
) {
3376 /* TODO: handle other OSC */
3380 term
->esc
= ESC_START
| ESC_TITLE
;
3383 } else if (term
->esc
& ESC_TITLE
) {
3384 int len
= utf8size(c
);
3386 if (ascii
== '\a' || term
->titlelen
+len
>= ESC_TITLE_SIZ
) {
3388 term
->title
[term
->titlelen
] = '\0';
3389 fixWindowTitle(term
);
3391 } else if (len
> 0) {
3392 memcpy(term
->title
+term
->titlelen
, c
, len
);
3393 term
->titlelen
+= len
;
3394 term
->title
[term
->titlelen
] = '\0';
3396 } else if (term
->esc
& ESC_ALTCHARSET
) {
3399 case '0': /* Line drawing crap */
3400 term
->mode
|= MODE_GFX0
;
3402 case 'B': /* Back to regular text */
3403 term
->mode
&= ~MODE_GFX0
;
3406 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
3407 term
->mode
&= ~MODE_GFX0
;
3410 } else if (term
->esc
& ESC_ALTG1
) {
3413 case '0': /* Line drawing crap */
3414 term
->mode
|= MODE_GFX1
;
3416 case 'B': /* Back to regular text */
3417 term
->mode
&= ~MODE_GFX1
;
3420 fprintf(stderr
, "esc unhandled charset: ESC ) %c\n", ascii
);
3421 term
->mode
&= ~MODE_GFX1
;
3424 } else if (term
->esc
& ESC_HASH
) {
3427 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
3428 //tfillscreenwithE();
3431 } else if (term
->esc
& ESC_PERCENT
) {
3438 term
->needConv
= (needConversion
? 1 : 0);
3443 case '[': term
->esc
|= ESC_CSI
; break;
3444 case ']': term
->esc
|= ESC_OSC
; break;
3445 case '(': term
->esc
|= ESC_ALTCHARSET
; break;
3446 case ')': term
->esc
|= ESC_ALTG1
; break;
3447 case '#': term
->esc
|= ESC_HASH
; break;
3448 case '%': term
->esc
|= ESC_PERCENT
; break;
3449 case 'D': /* IND -- Linefeed */
3451 if (term
->c
.y
== term
->bot
) tscrollup(term
->top
, 1, 1); else tmoveto(term
->c
.x
, term
->c
.y
+1);
3453 case 'E': /* NEL -- Next line */
3455 tnewline(1); /* always go to first col */
3457 case 'M': /* RI -- Reverse linefeed */
3459 if (term
->c
.y
== term
->top
) tscrolldown(term
->top
, 1); else tmoveto(term
->c
.x
, term
->c
.y
-1);
3461 case 'c': /* RIS -- Reset to inital state */
3465 case '=': /* DECPAM -- Application keypad */
3466 DUMP_KEYPAD_SWITCH("=", "ON");
3468 term
->mode
|= MODE_APPKEYPAD
;
3470 case '>': /* DECPNM -- Normal keypad */
3471 DUMP_KEYPAD_SWITCH(">", "OFF");
3473 term
->mode
&= ~MODE_APPKEYPAD
;
3475 case '7': /* DECSC -- Save Cursor */
3476 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
3479 tcursor(CURSOR_SAVE
);
3481 case '8': /* DECRC -- Restore Cursor */
3484 tcursor(CURSOR_LOAD
);
3486 case 'F': /* Cursor to lower left corner of screen */
3487 tmoveto(0, term
->row
-1);
3489 case 'Z': /* DEC private identification */
3491 ttywritestr("\x1b[?1;2c");
3495 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X ('%c')\n", (uchar
)ascii
, isprint(ascii
)?ascii
:'.');
3500 //if (term->sel.bx != -1 && BETWEEN(term->c.y, term->sel.by, term->sel.ey)) term->sel.bx = -1;
3502 if (term
->needConv
&& IS_GFX(term
->c
.attr
.attr
)) {
3506 if (cc
< 32 || cc
>= 127) break; //FIXME: nothing at all?
3508 if ((unsigned char)ascii
< 32 || ascii
== 127) break; // seems that this chars are empty too
3510 if (term
->c
.state
&CURSOR_WRAPNEXT
) {
3511 if (IS_SET(MODE_WRAP
)) {
3512 // always go to first col
3515 tsetcharwrap(term
->c
.y
, 0);
3516 break; // wrap is off, don't want more chars
3520 if (term
->c
.x
+1 < term
->col
) tmoveto(term
->c
.x
+1, term
->c
.y
); else term
->c
.state
|= CURSOR_WRAPNEXT
;
3526 static void tunshowhistory (void) {
3527 if (term
!= NULL
&& term
->topline
!= 0) {
3529 term
->wantRedraw
= 1;
3530 term
->lastDrawTime
= 0;
3535 static void tsendfocusevent (int focused
) {
3536 if (term
!= NULL
&& IS_SET(MODE_FOCUSEVT
)) {
3537 ttywritestr("\x1b[");
3538 ttywrite(focused
?"I":"O", 1);
3543 static void tcmdlinedirty (void) {
3545 markDirty(term
->row
-term
->topline
-1, 2);
3546 term
->wantRedraw
= 1;
3551 static void tcmdlinefixofs (void) {
3554 len
= utf8strlen(term
->cmdline
);
3555 ofs
= len
-(term
->col
-1);
3556 if (ofs
< 0) ofs
= 0;
3557 for (term
->cmdofs
= 0; ofs
> 0; --ofs
) term
->cmdofs
+= utf8size(term
->cmdline
+term
->cmdofs
);
3562 static void tcmdlinehide (void) {
3563 term
->cmdMode
= CMDMODE_NONE
;
3564 term
->cmdprevc
= NULL
;
3570 static void tcmdlinemsg (const char *msg
) {
3574 term
->cmdMode
= CMDMODE_MESSAGE
;
3576 term
->cmdtabpos
= -1;
3577 term
->cmdprevc
= NULL
;
3580 int len
= utf8size(msg
);
3582 if (len
< 1 || ofs
+len
>= sizeof(term
->cmdline
)-1) break;
3583 memcpy(term
->cmdline
+ofs
, msg
, len
);
3588 term
->cmdline
[ofs
] = 0;
3594 static __attribute__((format(printf
,1, 2))) void tcmdlinemsgf (const char *fmt
, ...) {
3597 int size
= sizeof(buf
)-1;
3605 n
= vsnprintf(xbuf
, size
, fmt
, ap
);
3607 if (n
> -1 && n
< size
) break;
3608 if (n
> -1) size
= n
+1; else size
+= 4096;
3609 if (xbuf
== buf
) xbuf
= NULL
;
3610 if ((t
= realloc(xbuf
, size
)) == NULL
) { if (xbuf
) free(xbuf
); return; }
3614 if (xbuf
!= buf
) free(xbuf
);
3618 static void tcmdlineinitex (const char *msg
) {
3619 term
->cmdMode
= CMDMODE_INPUT
;
3621 term
->cmdline
[0] = 0;
3624 term
->cmdtabpos
= -1;
3625 term
->cmdprevc
= NULL
;
3626 term
->cmdreslen
= 0;
3627 term
->cmdexecfn
= NULL
;
3628 if (msg
!= NULL
&& msg
[0]) {
3629 strcpy(term
->cmdline
, msg
);
3630 term
->cmdreslen
= strlen(term
->cmdline
);
3636 static void tcmdlineinit (void) {
3637 tcmdlineinitex(NULL
);
3641 static void tcmdlinechoplast (void) {
3642 if (term
->cmdcl
!= 0) {
3645 if (strlen(term
->cmdline
) > term
->cmdreslen
) utf8choplast(term
->cmdline
);
3652 static void tcmdaddchar (const char *s
) {
3653 int len
= utf8size(s
);
3656 int slen
= strlen(term
->cmdline
);
3658 if (slen
+len
< sizeof(term
->cmdline
)) {
3659 memcpy(term
->cmdline
+slen
, s
, len
);
3660 term
->cmdline
[slen
+len
] = 0;
3667 static void tcmdput (const char *s
, int len
) {
3671 term
->cmdc
[term
->cmdcl
++] = *s
++;
3672 term
->cmdc
[term
->cmdcl
] = 0;
3674 if ((ok
= isfullutf8(term
->cmdc
, term
->cmdcl
)) != 0 || term
->cmdcl
== UTF_SIZ
) {
3675 if (ok
) tcmdaddchar(term
->cmdc
);
3682 ////////////////////////////////////////////////////////////////////////////////
3684 static int tresize (int col
, int row
) {
3685 int mincol
= MIN(col
, term
->col
);
3686 int slide
= term
->c
.y
-row
+1;
3689 if (col
< 1 || row
< 1) return 0;
3693 g
.state
= GLYPH_DIRTY
;
3694 g
.attr
= ATTR_NULL
|ATTR_DEFFG
|ATTR_DEFBG
;
3701 tsetscroll(0, term
->row
-1);
3702 for (; slide
> 0; --slide
) tscrollup(0, 1, 1); // to fill history
3705 if (row
< term
->row
) {
3706 /* free unneeded rows */
3707 for (int f
= row
; f
< term
->row
; ++f
) free(term
->alt
[f
]);
3708 for (int f
= term
->linecount
-(term
->row
-row
); f
< term
->linecount
; ++f
) free(term
->line
[f
]);
3709 term
->linecount
-= (term
->row
-row
);
3710 /* resize to new height */
3711 term
->alt
= realloc(term
->alt
, row
*sizeof(Line
));
3712 term
->line
= realloc(term
->line
, term
->linecount
*sizeof(Line
));
3713 } else if (row
> term
->row
) {
3714 /* resize to new height */
3715 term
->alt
= realloc(term
->alt
, row
*sizeof(Line
));
3716 term
->line
= realloc(term
->line
, (row
+term
->maxhistory
)*sizeof(Line
));
3717 /* add more lines */
3718 for (int f
= term
->row
; f
< row
; ++f
) {
3719 term
->alt
[f
] = calloc(col
, sizeof(Glyph
));
3720 for (int x
= 0; x
< col
; ++x
) term
->alt
[f
][x
] = g
;
3722 for (int f
= 0; f
< row
-term
->row
; ++f
) {
3723 int y
= term
->linecount
++;
3725 term
->line
[y
] = calloc(col
, sizeof(Glyph
));
3726 for (int x
= 0; x
< col
; ++x
) term
->line
[y
][x
] = g
;
3730 if (row
!= term
->row
) {
3731 term
->dirty
= realloc(term
->dirty
, row
*sizeof(*term
->dirty
));
3734 /* resize each row to new width, zero-pad if needed */
3735 for (int f
= 0; f
< term
->linecount
; ++f
) {
3736 term
->line
[f
] = realloc(term
->line
[f
], col
*sizeof(Glyph
));
3737 for (int x
= mincol
; x
< col
; ++x
) term
->line
[f
][x
] = g
;
3740 term
->alt
[f
] = realloc(term
->alt
[f
], col
*sizeof(Glyph
));
3741 for (int x
= mincol
; x
< col
; ++x
) term
->alt
[f
][x
] = g
;
3744 /* update terminal size */
3748 /* make use of the LIMIT in tmoveto */
3749 tmoveto(term
->c
.x
, term
->c
.y
);
3750 /* reset scrolling region */
3751 tsetscroll(0, row
-1);
3757 static void xresize (int col
, int row
) {
3761 if (term
== NULL
) return;
3762 oldw
= term
->picbufw
;
3763 oldh
= term
->picbufh
;
3764 term
->picbufw
= MAX(1, col
*xw
.cw
);
3765 term
->picbufh
= MAX(1, row
*xw
.ch
);
3766 newbuf
= XCreatePixmap(xw
.dpy
, xw
.win
, term
->picbufw
, term
->picbufh
, XDefaultDepth(xw
.dpy
, xw
.scr
));
3767 XCopyArea(xw
.dpy
, term
->picbuf
, newbuf
, dc
.gc
, 0, 0, term
->picbufw
, term
->picbufh
, 0, 0);
3768 XFreePixmap(xw
.dpy
, term
->picbuf
);
3769 XSetForeground(xw
.dpy
, dc
.gc
, getColor(term
->defbg
));
3770 if (term
->picbufw
> oldw
) {
3771 XFillRectangle(xw
.dpy
, newbuf
, dc
.gc
, oldw
, 0, term
->picbufw
-oldw
, MIN(term
->picbufh
, oldh
));
3772 } else if (term
->picbufw
< oldw
&& xw
.w
> term
->picbufw
) {
3773 XClearArea(xw
.dpy
, xw
.win
, term
->picbufw
, 0, xw
.w
-term
->picbufh
, MIN(term
->picbufh
, oldh
), False
);
3775 if (term
->picbufh
> oldh
) {
3776 XFillRectangle(xw
.dpy
, newbuf
, dc
.gc
, 0, oldh
, term
->picbufw
, term
->picbufh
-oldh
);
3777 } else if (term
->picbufh
< oldh
&& xw
.h
> term
->picbufh
) {
3778 XClearArea(xw
.dpy
, xw
.win
, 0, term
->picbufh
, xw
.w
, xw
.h
-term
->picbufh
, False
);
3780 term
->picbuf
= newbuf
;
3786 ////////////////////////////////////////////////////////////////////////////////
3787 // x11 drawing and utils
3789 static void xcreatebw (void) {
3790 if ((dc.bcol = calloc(MAX_COLOR+1, sizeof(dc.bcol[0]))) == NULL) die("out of memory");
3792 for (int f = 0; f <= MAX_COLOR; ++f) {
3795 nclr = dc.ncol[f].pixel;
3796 XQueryColor(xw.dpy, xw.cmap, &nclr);
3797 fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", f, nclr.red, nclr.green, nclr.blue);
3803 static void xallocbwclr (int idx
, XColor
*color
) {
3806 XQueryColor(xw
.dpy
, xw
.cmap
, color
);
3807 //fprintf(stderr, "%d: r=%u; g=%u; b=%u\n", idx, color->red, color->green, color->blue);
3809 lumi
= 0.3*((double)color
->red
/65535.0)+0.59*((double)color
->green
/65535.0)+0.11*((double)color
->blue
/65535.0);
3810 color
->red
= color
->green
= color
->blue
= (int)(lumi
*65535.0);
3811 if (!XAllocColor(xw
.dpy
, xw
.cmap
, color
)) {
3812 fprintf(stderr
, "WARNING: could not allocate b/w color #%d\n", idx
);
3815 dc
.bcol
[idx
] = color
->pixel
;
3816 color
->red
= color
->blue
= 0;
3817 if (!XAllocColor(xw
.dpy
, xw
.cmap
, color
)) {
3818 fprintf(stderr
, "WARNING: could not allocate b/w color #%d\n", idx
);
3821 dc
.gcol
[idx
] = color
->pixel
;
3825 static void xallocnamedclr (int idx
, const char *cname
) {
3828 if (!XAllocNamedColor(xw
.dpy
, xw
.cmap
, cname
, &color
, &color
)) {
3829 fprintf(stderr
, "WARNING: could not allocate color #%d: '%s'\n", idx
, cname
);
3832 dc
.ncol
[idx
] = color
.pixel
;
3833 xallocbwclr(idx
, &color
);
3837 static void xloadcols (void) {
3840 uint32 white
= WhitePixel(xw
.dpy
, xw
.scr
);
3842 if ((dc
.clrs
[0] = dc
.ncol
= calloc(MAX_COLOR
+1, sizeof(dc
.ncol
[0]))) == NULL
) die("out of memory");
3843 if ((dc
.clrs
[1] = dc
.bcol
= calloc(MAX_COLOR
+1, sizeof(dc
.bcol
[0]))) == NULL
) die("out of memory");
3844 if ((dc
.clrs
[2] = dc
.gcol
= calloc(MAX_COLOR
+1, sizeof(dc
.gcol
[0]))) == NULL
) die("out of memory");
3846 for (f
= 0; f
<= MAX_COLOR
; ++f
) dc
.ncol
[f
] = dc
.bcol
[f
] = white
;
3847 /* load colors [0-15] */
3848 for (f
= 0; f
<= 15; ++f
) {
3849 const char *cname
= opt_colornames
[f
]!=NULL
?opt_colornames
[f
]:defcolornames
[f
];
3851 xallocnamedclr(f
, cname
);
3853 /* load colors [256-...] */
3854 for (f
= 256; f
<= MAX_COLOR
; ++f
) {
3855 const char *cname
= opt_colornames
[f
];
3857 if (cname
== NULL
) {
3858 if (LEN(defextcolornames
) <= f
-256) continue;
3859 cname
= defextcolornames
[f
-256];
3861 if (cname
== NULL
) continue;
3862 xallocnamedclr(f
, cname
);
3864 /* load colors [16-255] ; same colors as xterm */
3865 for (f
= 16, r
= 0; r
< 6; ++r
) {
3866 for (g
= 0; g
< 6; ++g
) {
3867 for (b
= 0; b
< 6; ++b
) {
3868 if (opt_colornames
[f
] != NULL
) {
3869 xallocnamedclr(f
, opt_colornames
[f
]);
3871 color
.red
= r
== 0 ? 0 : 0x3737+0x2828*r
;
3872 color
.green
= g
== 0 ? 0 : 0x3737+0x2828*g
;
3873 color
.blue
= b
== 0 ? 0 : 0x3737+0x2828*b
;
3874 if (!XAllocColor(xw
.dpy
, xw
.cmap
, &color
)) {
3875 fprintf(stderr
, "WARNING: could not allocate color #%d\n", f
);
3877 dc
.ncol
[f
] = color
.pixel
;
3878 xallocbwclr(f
, &color
);
3885 for (r
= 0; r
< 24; ++r
, ++f
) {
3886 if (opt_colornames
[f
] != NULL
) {
3887 xallocnamedclr(f
, opt_colornames
[f
]);
3889 color
.red
= color
.green
= color
.blue
= 0x0808+0x0a0a*r
;
3890 if (!XAllocColor(xw
.dpy
, xw
.cmap
, &color
)) {
3891 fprintf(stderr
, "WARNING: could not allocate color #%d\n", f
);
3893 dc
.ncol
[f
] = color
.pixel
;
3894 xallocbwclr(f
, &color
);
3899 for (int f
= 0; f
< LEN(opt_colornames
); ++f
) if (opt_colornames
[f
]) free(opt_colornames
[f
]);
3903 static void xclear (int x1
, int y1
, int x2
, int y2
) {
3904 XSetForeground(xw
.dpy
, dc
.gc
, getColor(IS_SET(MODE_REVERSE
) ? term
->deffg
: term
->defbg
));
3905 XFillRectangle(xw
.dpy
, term
->picbuf
, dc
.gc
, x1
*xw
.cw
, y1
*xw
.ch
, (x2
-x1
+1)*xw
.cw
, (y2
-y1
+1)*xw
.ch
);
3909 static void xhints (void) {
3910 XClassHint
class = {opt_class
, opt_title
};
3911 XWMHints wm
= {.flags
= InputHint
, .input
= 1};
3913 .flags
= PSize
| PResizeInc
| PBaseSize
,
3916 .height_inc
= xw
.ch
,
3918 .base_height
= xw
.h
/*xw.tabheight*/,
3921 //XSetWMNormalHints(xw.dpy, xw.win, &size);
3922 XSetWMProperties(xw
.dpy
, xw
.win
, NULL
, NULL
, NULL
, 0, &size
, &wm
, &class);
3923 XSetWMProtocols(xw
.dpy
, xw
.win
, &XA_WM_DELETE_WINDOW
, 1);
3927 static XFontSet
xinitfont (const char *fontstr
) {
3929 char *def
, **missing
;
3933 set
= XCreateFontSet(xw
.dpy
, fontstr
, &missing
, &n
, &def
);
3935 while (n
--) fprintf(stderr
, "sterm: missing fontset: %s\n", missing
[n
]);
3936 XFreeStringList(missing
);
3942 static void xgetfontinfo (XFontSet set
, int *ascent
, int *descent
, short *lbearing
, short *rbearing
, Font
*fid
) {
3943 XFontStruct
**xfonts
;
3947 *ascent
= *descent
= *lbearing
= *rbearing
= 0;
3948 n
= XFontsOfFontSet(set
, &xfonts
, &font_names
);
3949 for (int f
= 0; f
< n
; ++f
) {
3950 if (f
== 0) *fid
= (*xfonts
)->fid
;
3951 *ascent
= MAX(*ascent
, (*xfonts
)->ascent
);
3952 *descent
= MAX(*descent
, (*xfonts
)->descent
);
3953 *lbearing
= MAX(*lbearing
, (*xfonts
)->min_bounds
.lbearing
);
3954 *rbearing
= MAX(*rbearing
, (*xfonts
)->max_bounds
.rbearing
);
3960 static void initfonts (const char *fontstr
, const char *bfontstr
, const char *tabfont
) {
3961 if ((dc
.font
[0].set
= xinitfont(fontstr
)) == NULL
) {
3962 if ((dc
.font
[0].set
= xinitfont(FONT
)) == NULL
) die("can't load font %s", fontstr
);
3964 xgetfontinfo(dc
.font
[0].set
, &dc
.font
[0].ascent
, &dc
.font
[0].descent
, &dc
.font
[0].lbearing
, &dc
.font
[0].rbearing
, &dc
.font
[0].fid
);
3966 if ((dc
.font
[1].set
= xinitfont(bfontstr
)) == NULL
) {
3967 if ((dc
.font
[1].set
= xinitfont(FONTBOLD
)) == NULL
) die("can't load font %s", bfontstr
);
3969 xgetfontinfo(dc
.font
[1].set
, &dc
.font
[1].ascent
, &dc
.font
[1].descent
, &dc
.font
[1].lbearing
, &dc
.font
[1].rbearing
, &dc
.font
[1].fid
);
3971 if ((dc
.font
[2].set
= xinitfont(tabfont
)) == NULL
) {
3972 if ((dc
.font
[2].set
= xinitfont(FONTTAB
)) == NULL
) die("can't load font %s", tabfont
);
3974 xgetfontinfo(dc
.font
[2].set
, &dc
.font
[2].ascent
, &dc
.font
[2].descent
, &dc
.font
[2].lbearing
, &dc
.font
[2].rbearing
, &dc
.font
[2].fid
);
3978 static void xinit (void) {
3979 XSetWindowAttributes attrs
;
3981 XColor blackcolor
= { 0, 0, 0, 0, 0, 0 };
3983 if (!(xw
.dpy
= XOpenDisplay(NULL
))) die("can't open display");
3985 XA_VT_SELECTION
= XInternAtom(xw
.dpy
, "_STERM_SELECTION_", 0);
3986 XA_CLIPBOARD
= XInternAtom(xw
.dpy
, "CLIPBOARD", 0);
3987 XA_UTF8
= XInternAtom(xw
.dpy
, "UTF8_STRING", 0);
3988 XA_NETWM_NAME
= XInternAtom(xw
.dpy
, "_NET_WM_NAME", 0);
3989 XA_TARGETS
= XInternAtom(xw
.dpy
, "TARGETS", 0);
3990 XA_WM_DELETE_WINDOW
= XInternAtom(xw
.dpy
, "WM_DELETE_WINDOW", 0);
3991 xw
.xembed
= XInternAtom(xw
.dpy
, "_XEMBED", False
);
3993 xw
.scr
= XDefaultScreen(xw
.dpy
);
3995 initfonts(opt_fontnorm
, opt_fontbold
, opt_fonttab
);
3996 /* XXX: Assuming same size for bold font */
3997 xw
.cw
= dc
.font
[0].rbearing
-dc
.font
[0].lbearing
;
3998 xw
.ch
= dc
.font
[0].ascent
+dc
.font
[0].descent
;
3999 xw
.tch
= dc
.font
[2].ascent
+dc
.font
[2].descent
;
4000 xw
.tabheight
= opt_disabletabs
? 0 : xw
.tch
+2;
4003 xw
.cmap
= XDefaultColormap(xw
.dpy
, xw
.scr
);
4005 /* window - default size */
4006 term
->picbufh
= term
->row
*xw
.ch
;
4007 term
->picbufw
= term
->col
*xw
.cw
;
4009 xw
.h
= term
->picbufh
+xw
.tabheight
;
4010 xw
.w
= term
->picbufw
;
4012 attrs
.background_pixel
= getColor(defaultBG
);
4013 attrs
.border_pixel
= getColor(defaultBG
);
4014 attrs
.bit_gravity
= NorthWestGravity
;
4015 attrs
.event_mask
= FocusChangeMask
| KeyPressMask
4016 | ExposureMask
| VisibilityChangeMask
| StructureNotifyMask
4017 | /*ButtonMotionMask*/ PointerMotionMask
| ButtonPressMask
| ButtonReleaseMask
4018 | EnterWindowMask
| LeaveWindowMask
;
4019 attrs
.colormap
= xw
.cmap
;
4020 //fprintf(stderr, "oe: [%s]\n", opt_embed);
4021 parent
= opt_embed
? strtol(opt_embed
, NULL
, 0) : XRootWindow(xw
.dpy
, xw
.scr
);
4022 xw
.win
= XCreateWindow(xw
.dpy
, parent
, 0, 0,
4023 xw
.w
, xw
.h
, 0, XDefaultDepth(xw
.dpy
, xw
.scr
), InputOutput
,
4024 XDefaultVisual(xw
.dpy
, xw
.scr
),
4025 CWBackPixel
| CWBorderPixel
| CWBitGravity
| CWEventMask
4029 term
->picbuf
= XCreatePixmap(xw
.dpy
, xw
.win
, term
->picbufw
, term
->picbufh
, XDefaultDepth(xw
.dpy
, xw
.scr
));
4030 xw
.pictab
= XCreatePixmap(xw
.dpy
, xw
.win
, xw
.w
, xw
.tabheight
>0?xw
.tabheight
:1, XDefaultDepth(xw
.dpy
, xw
.scr
));
4032 if ((xw
.xim
= XOpenIM(xw
.dpy
, NULL
, NULL
, NULL
)) == NULL
) die("XOpenIM() failed");
4033 xw
.xic
= XCreateIC(xw
.xim
, XNInputStyle
, XIMPreeditNothing
| XIMStatusNothing
, XNClientWindow
, xw
.win
, XNFocusWindow
, xw
.win
, NULL
);
4035 dc
.gc
= XCreateGC(xw
.dpy
, xw
.win
, 0, NULL
);
4036 /* white cursor, black outline */
4037 xw
.cursor
= XCreateFontCursor(xw
.dpy
, XC_xterm
);
4038 XDefineCursor(xw
.dpy
, xw
.win
, xw
.cursor
);
4039 XRecolorCursor(xw
.dpy
, xw
.cursor
,
4040 &(XColor
){.red
= 0xffff, .green
= 0xffff, .blue
= 0xffff},
4041 &(XColor
){.red
= 0x0000, .green
= 0x0000, .blue
= 0x0000});
4042 fixWindowTitle(term
);
4043 //XStoreName(xw.dpy, xw.win, opt_title);
4045 XSetForeground(xw
.dpy
, dc
.gc
, 0);
4046 XFillRectangle(xw
.dpy
, term
->picbuf
, dc
.gc
, 0, 0, term
->picbufw
, term
->picbufh
);
4047 if (xw
.tabheight
> 0) XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, 0, 0, xw
.w
, xw
.tabheight
);
4049 XMapWindow(xw
.dpy
, xw
.win
);
4051 #if BLANKPTR_USE_GLYPH_CURSOR
4052 xw
.blankPtr
= XCreateGlyphCursor(xw
.dpy
, dc
.font
[0].fid
, dc
.font
[0].fid
, ' ', ' ', &blackcolor
, &blackcolor
);
4054 static const char cmbmp
[1] = {0};
4057 pm
= XCreateBitmapFromData(xw
.dpy
, xw
.win
, cmbmp
, 1, 1);
4058 xw
.blankPtr
= XCreatePixmapCursor(xw
.dpy
, pm
, pm
, &blackcolor
, &blackcolor
, 0, 0);
4059 XFreePixmap(xw
.dpy
, pm
);
4066 static void xblankPointer (void) {
4067 if (!ptrBlanked
&& xw
.blankPtr
!= None
) {
4069 XDefineCursor(xw
.dpy
, xw
.win
, xw
.blankPtr
);
4075 static void xunblankPointer (void) {
4076 if (ptrBlanked
&& xw
.cursor
!= None
) {
4078 XDefineCursor(xw
.dpy
, xw
.win
, xw
.cursor
);
4080 ptrLastMove
= mclock_ticks();
4085 static void xdraws (const char *s
, const Glyph
*base
, int x
, int y
, int charlen
, int bytelen
) {
4086 int fg
= base
->fg
, bg
= base
->bg
, temp
;
4087 int winx
= x
*xw
.cw
, winy
= y
*xw
.ch
+dc
.font
[0].ascent
, width
= charlen
*xw
.cw
;
4088 XFontSet fontset
= dc
.font
[0].set
;
4089 int defF
= base
->attr
&ATTR_DEFFG
, defB
= base
->attr
&ATTR_DEFBG
;
4091 /* only switch default fg/bg if term is in RV mode */
4092 if (IS_SET(MODE_REVERSE
)) {
4093 if (defF
) fg
= term
->defbg
;
4094 if (defB
) bg
= term
->deffg
;
4096 if (base
->attr
&ATTR_REVERSE
) defF
= defB
= 0;
4097 if (base
->attr
&ATTR_BOLD
) {
4098 if (defF
&& defB
&& defaultBoldFG
>= 0) fg
= defaultBoldFG
;
4099 else if (fg
< 8) fg
+= 8;
4100 fontset
= dc
.font
[1].set
;
4102 if ((base
->attr
&ATTR_UNDERLINE
) && defaultUnderlineFG
>= 0) {
4103 if (defF
&& defB
) fg
= defaultUnderlineFG
;
4106 if (base
->attr
&ATTR_REVERSE
) { temp
= fg
; fg
= bg
; bg
= temp
; }
4108 XSetBackground(xw
.dpy
, dc
.gc
, getColor(bg
));
4109 XSetForeground(xw
.dpy
, dc
.gc
, getColor(fg
));
4113 FILE *fo = fopen("zlog.log", "ab");
4114 fprintf(fo, "xdraws: x=%d; y=%d; charlen=%d; bytelen=%d\n", x, y, charlen, bytelen);
4119 if (IS_GFX(base
->attr
)) {
4120 XmbDrawImageString(xw
.dpy
, term
->picbuf
, fontset
, dc
.gc
, winx
, winy
, s
, bytelen
);
4121 } else if (!needConversion
) {
4122 XmbDrawImageString(xw
.dpy
, term
->picbuf
, fontset
, dc
.gc
, winx
, winy
, s
, bytelen
);
4125 //k8: dunno why, but Xutf8DrawImageString() ignores ascii (at least for terminus)
4126 const char *pos
= s
;
4129 while (pos
< s
+bytelen
) {
4133 if ((unsigned char)(pos
[0]) < 128) {
4134 for (e
= pos
+1; e
< s
+bytelen
&& (unsigned char)(*e
) < 128; ++e
) ;
4136 XmbDrawImageString(xw
.dpy
, term
->picbuf
, fontset
, dc
.gc
, xpos
, winy
, pos
, e
-pos
);
4139 FILE *fo = fopen("zlog.log", "ab");
4140 fprintf(fo, " norm: clen=%d (%d); [", clen, (int)(e-pos));
4141 fwrite(pos, 1, e-pos, fo);
4147 for (clen
= 0, e
= pos
; e
< s
+bytelen
&& (unsigned char)(*e
) >= 128; ++e
) {
4148 if (((unsigned char)(e
[0])&0xc0) == 0xc0) ++clen
;
4150 Xutf8DrawImageString(xw
.dpy
, term
->picbuf
, fontset
, dc
.gc
, xpos
, winy
, pos
, e
-pos
);
4153 FILE *fo = fopen("zlog.log", "ab");
4154 fprintf(fo, " utf8: clen=%d (%d); [", clen, (int)(e-pos));
4155 fwrite(pos, 1, e-pos, fo);
4167 if (opt_drawunderline
&& (base
->attr
&ATTR_UNDERLINE
)) {
4168 XDrawLine(xw
.dpy
, term
->picbuf
, dc
.gc
, winx
, winy
+1, winx
+width
-1, winy
+1);
4173 /* copy buffer pixmap to screen pixmap */
4174 static void xcopy (int x
, int y
, int cols
, int rows
) {
4175 int src_x
= x
*xw
.cw
, src_y
= y
*xw
.ch
, src_w
= cols
*xw
.cw
, src_h
= rows
*xw
.ch
;
4176 int dst_x
= src_x
, dst_y
= src_y
;
4178 if (opt_tabposition
== 1) { dst_y
+= xw
.tabheight
; }
4179 XCopyArea(xw
.dpy
, term
->picbuf
, xw
.win
, dc
.gc
, src_x
, src_y
, src_w
, src_h
, dst_x
, dst_y
);
4183 static void xdrawcursor (void) {
4185 int sl
, scrx
, scry
, cmy
;
4187 if (term
== NULL
) return;
4189 LIMIT(term
->oldcx
, 0, term
->col
-1);
4190 LIMIT(term
->oldcy
, 0, term
->row
-1);
4192 cmy
= term
->row
-term
->topline
-1;
4194 if (!(xw
.state
&WIN_FOCUSED
) && !term
->curblinkinactive
) term
->curbhidden
= 0;
4196 if (term
->cmdMode
== CMDMODE_NONE
|| term
->oldcy
!= cmy
) {
4198 scry
= term
->oldcy
+term
->topline
;
4199 if (scry
>= 0 && scry
< term
->row
) {
4200 if (term
->curbhidden
< 0 ||
4201 term
->oldcy
!= term
->c
.y
|| term
->oldcx
!= term
->c
.x
||
4202 (term
->c
.state
&CURSOR_HIDE
) ||
4203 !(xw
.state
&WIN_FOCUSED
)) {
4204 /* remove the old cursor */
4205 sl
= utf8size(term
->line
[term
->oldcy
][scrx
].c
);
4206 g
= term
->line
[term
->oldcy
][scrx
];
4207 if (selected(scrx
, term
->c
.y
)) g
.attr
^= ATTR_REVERSE
;
4208 xdraws(g
.c
, &g
, scrx
, scry
, 1, sl
);
4209 //xclear(scrx, term->oldcy, scrx, term->oldcy);
4210 xcopy(scrx
, scry
, 1, 1);
4212 if (term
->curbhidden
) term
->curbhidden
= 1;
4216 if (term
->cmdMode
!= CMDMODE_NONE
&& term
->oldcy
== cmy
) return;
4217 if ((term
->c
.state
&CURSOR_HIDE
) != 0) return;
4218 if (term
->curbhidden
) return;
4219 /* draw the new one */
4221 scry
= term
->c
.y
+term
->topline
;
4222 if (scry
>= 0 && scry
< term
->row
) {
4225 if (!(xw
.state
&WIN_FOCUSED
)) {
4226 if (defaultCursorInactiveBG
< 0) {
4227 XSetForeground(xw
.dpy
, dc
.gc
, getColor(defaultCursorBG
));
4228 XDrawRectangle(xw
.dpy
, term
->picbuf
, dc
.gc
, scrx
*xw
.cw
, scry
*xw
.ch
, xw
.cw
-1, xw
.ch
-1);
4231 g
.bg
= defaultCursorInactiveBG
;
4232 g
.fg
= defaultCursorInactiveFG
;
4235 g
.fg
= defaultCursorFG
;
4236 g
.bg
= defaultCursorBG
;
4239 g
.uc
= term
->line
[term
->c
.y
][scrx
].uc
;
4242 if (IS_SET(MODE_REVERSE
)) g
.attr
|= ATTR_REVERSE
;
4244 xdraws(g
.c
, &g
, scrx
, scry
, 1, sl
);
4247 term
->oldcy
= term
->c
.y
;
4248 xcopy(scrx
, scry
, 1, 1);
4253 static void xdrawTabBar (void) {
4254 if (xw
.tabheight
> 0 && updateTabBar
) {
4255 if (updateTabBar
> 0) {
4256 int tabw
= xw
.w
/opt_tabcount
, n
= (opt_tabposition
== 0 ? 1 : 0);
4257 XFontSet fontset
= dc
.font
[2].set
;
4259 XSetForeground(xw
.dpy
, dc
.gc
, getColor(normalTabBG
));
4260 XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, 0, 0, xw
.w
, xw
.tabheight
);
4262 for (int f
= firstVisibleTab
; f
< firstVisibleTab
+opt_tabcount
; ++f
) {
4263 int x
= (f
-firstVisibleTab
)*tabw
;
4267 if (f
>= term_count
) {
4268 XSetForeground(xw
.dpy
, dc
.gc
, getColor(normalTabBG
));
4269 XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, x
, 0, xw
.w
, xw
.tabheight
);
4271 XSetForeground(xw
.dpy
, dc
.gc
, getColor(normalTabFG
));
4272 XDrawLine(xw
.dpy
, xw
.pictab
, dc
.gc
, x
/*+tabw-1*/, 0, x
/*+tabw-1*/, xw
.tabheight
);
4275 title
= term_array
[f
]->title
;
4276 if (!title
[0]) title
= opt_title
;
4277 tit
= SPrintf("[%d]%s", f
, title
);
4280 XSetForeground(xw
.dpy
, dc
.gc
, getColor(f
== termidx
? activeTabBG
: normalTabBG
));
4281 XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, x
, 0, tabw
, xw
.tabheight
);
4283 XSetBackground(xw
.dpy
, dc
.gc
, getColor(f
== termidx
? activeTabBG
: normalTabBG
));
4284 XSetForeground(xw
.dpy
, dc
.gc
, getColor(f
== termidx
? activeTabFG
: normalTabFG
));
4286 if (needConversion
) {
4289 while (*title
&& xx
< x
+tabw
) {
4290 const char *e
= title
;
4293 memset(&r
, 0, sizeof(r
));
4295 if ((unsigned char)(*e
) > 127) {
4296 while (*e
&& (unsigned char)(*e
) > 127) ++e
;
4297 Xutf8DrawImageString(xw
.dpy
, xw
.pictab
, fontset
, dc
.gc
, xx
, dc
.font
[2].ascent
+n
, title
, e
-title
);
4298 Xutf8TextExtents(fontset
, title
, e
-title
, &r
, NULL
);
4300 while (*e
&& (unsigned char)(*e
) <= 127) ++e
;
4301 XmbDrawImageString(xw
.dpy
, xw
.pictab
, fontset
, dc
.gc
, xx
, dc
.font
[2].ascent
+n
, title
, e
-title
);
4302 XmbTextExtents(fontset
, title
, e
-title
, &r
, NULL
);
4308 XmbDrawImageString(xw
.dpy
, xw
.pictab
, fontset
, dc
.gc
, x
+2, dc
.font
[2].ascent
+n
, title
, strlen(title
));
4310 Xutf8DrawImageString(xw
.dpy
, xw
.pictab
, fontset
, dc
.gc
, x
+2, dc
.font
[2].ascent
+n
, title
, strlen(title
));
4314 XSetForeground(xw
.dpy
, dc
.gc
, getColor(f
== termidx
? activeTabBG
: normalTabBG
));
4315 XFillRectangle(xw
.dpy
, xw
.pictab
, dc
.gc
, x
+tabw
-2, 0, 2, xw
.tabheight
);
4317 if (f
> firstVisibleTab
) {
4318 XSetForeground(xw
.dpy
, dc
.gc
, getColor(normalTabFG
));
4319 XDrawLine(xw
.dpy
, xw
.pictab
, dc
.gc
, x
/*+tabw-1*/, 0, x
/*+tabw-1*/, xw
.tabheight
);
4323 XSetForeground(xw
.dpy
, dc
.gc
, getColor(normalTabFG
));
4324 //XDrawRectangle(xw.dpy, xw.pictab, dc.gc, -1, 0, xw.w+1, xw.tabheight+1);
4325 if (opt_tabposition
== 0) {
4326 XDrawLine(xw
.dpy
, xw
.pictab
, dc
.gc
, 0, 0, xw
.w
, 0);
4328 XDrawLine(xw
.dpy
, xw
.pictab
, dc
.gc
, 0, xw
.tabheight
-1, xw
.w
, xw
.tabheight
-1);
4332 if (opt_tabposition
== 0) {
4333 XCopyArea(xw
.dpy
, xw
.pictab
, xw
.win
, dc
.gc
, 0, 0, xw
.w
, xw
.tabheight
, 0, xw
.h
-xw
.tabheight
);
4335 XCopyArea(xw
.dpy
, xw
.pictab
, xw
.win
, dc
.gc
, 0, 0, xw
.w
, xw
.tabheight
, 0, 0);
4342 static void drawcmdline (int scry
) {
4344 int cpos
= term
->cmdofs
, bc
= 0, x
, sx
;
4345 int back
= (term
->cmdMode
== CMDMODE_INPUT
? 21 : 124);
4346 int rslen
= (term
->cmdMode
== CMDMODE_INPUT
? term
->cmdreslen
: 32760);
4348 base
.attr
= ATTR_NULL
;
4353 for (sx
= x
= 0; x
< term
->col
&& term
->cmdline
[cpos
] && cpos
< rslen
; ++x
) {
4354 int l
= utf8size(term
->cmdline
+cpos
);
4356 if (bc
+l
> DRAW_BUF_SIZ
) {
4357 xdraws(term
->drawbuf
, &base
, sx
, scry
, x
-sx
, bc
);
4361 memcpy(term
->drawbuf
+bc
, term
->cmdline
+cpos
, l
);
4365 if (bc
> 0) xdraws(term
->drawbuf
, &base
, sx
, scry
, x
-sx
, bc
);
4369 for (sx
= x
; x
< term
->col
&& term
->cmdline
[cpos
]; ++x
) {
4370 int l
= utf8size(term
->cmdline
+cpos
);
4372 if (bc
+l
> DRAW_BUF_SIZ
) {
4373 xdraws(term
->drawbuf
, &base
, sx
, scry
, x
-sx
, bc
);
4377 memcpy(term
->drawbuf
+bc
, term
->cmdline
+cpos
, l
);
4381 if (bc
> 0) xdraws(term
->drawbuf
, &base
, sx
, scry
, x
-sx
, bc
);
4383 if (x
< term
->col
&& term
->cmdMode
== CMDMODE_INPUT
) {
4386 xdraws(" ", &base
, x
, scry
, 1, 1);
4390 if (x
< term
->col
) {
4393 memset(term
->drawbuf
, ' ', DRAW_BUF_SIZ
);
4394 while (x
< term
->col
) {
4397 if (x
> term
->col
) x
= term
->col
;
4398 xdraws(term
->drawbuf
, &base
, sx
, scry
, x
-sx
, x
-sx
);
4402 xcopy(0, scry
, term
->col
, 1);
4406 static void drawline (int x1
, int x2
, int scry
, int lineno
, int dontcopy
) {
4407 //fprintf(stderr, "%d: drawline: x1=%d; x2=%d; scry=%d; row:%d; lineno=%d\n", mclock_ticks(), x1, x2, scry, term->row, lineno);
4408 if (scry
< 0 || scry
>= term
->row
) return;
4409 if (scry
== term
->row
-1 && term
->cmdMode
!= CMDMODE_NONE
) { drawcmdline(scry
); return; }
4410 if (lineno
< 0 || lineno
>= term
->linecount
) {
4411 xclear(0, scry
, term
->col
-1, scry
);
4412 xcopy(0, scry
, term
->col
, 1);
4417 Line l
= term
->line
[lineno
];
4419 if (lineno
< term
->row
&& term
->topline
== 0) {
4420 if (!term
->dirty
[lineno
]) return;
4422 // fix 'dirty' flag for line
4423 if (term
->dirty
[lineno
]&0x02) {
4424 // mark full line as dirty
4427 term
->dirty
[lineno
] = 0;
4429 term
->dirty
[lineno
] = 0;
4430 if (x1
> 0) for (int x
= 0; x
< x1
; ++x
) if (l
[x
].state
&GLYPH_DIRTY
) { term
->dirty
[lineno
] = 1; break; }
4431 if (!term
->dirty
[lineno
] && x2
< term
->col
) for (int x
= x2
; x
< term
->col
; ++x
) if (l
[x
].state
&GLYPH_DIRTY
) { term
->dirty
[lineno
] = 1; break; }
4433 // find dirty region
4434 for (stx
= x1
; stx
< x2
; ++stx
) if (l
[stx
].state
&GLYPH_DIRTY
) break;
4435 for (ex
= x2
; ex
> stx
; --ex
) if (l
[ex
-1].state
&GLYPH_DIRTY
) break;
4436 if (stx
>= x2
|| ex
<= stx
) return; // nothing to do
4439 // 'dontcopy' means that the whole screen is dirty
4442 term
->dirty
[lineno
] = 0;
4445 //if (lineno < term->row) term->dirty[lineno] = 0;
4451 if (term
->sel
.bx
!= -1 && selected(stx
, lineno
)) base
.attr
^= ATTR_REVERSE
;
4454 //xclear(stx, scry, ex-1, scry); //k8: actually, we don't need this for good monospace fonts, so...
4455 for (int x
= stx
; x
< ex
; ++x
) {
4457 l
[x
].state
&= ~GLYPH_DIRTY
; //!
4458 if (term
->sel
.bx
!= -1 && selected(x
, lineno
)) new.attr
^= ATTR_REVERSE
;
4459 if (ib
> 0 && (ATTRCMP(base
, new) || ib
>= DRAW_BUF_SIZ
-UTF_SIZ
)) {
4460 // flush draw buffer
4461 xdraws(term
->drawbuf
, &base
, ox
, scry
, ic
, ib
);
4464 if (ib
== 0) { ox
= x
; base
= new; }
4465 sl
= utf8size(new.c
);
4466 memcpy(term
->drawbuf
+ib
, new.c
, sl
);
4470 if (ib
> 0) xdraws(term
->drawbuf
, &base
, ox
, scry
, ic
, ib
);
4471 //xcopy(0, scry, term->col, 1);
4472 //if (term->c.y == lineno && term->c.x >= stx && term->c.x < ex) xdrawcursor();
4473 if (!dontcopy
) xcopy(stx
, scry
, ex
-stx
, 1);
4478 static void drawregion (int x1
, int y1
, int x2
, int y2
, int forced
) {
4481 if (!forced
&& (xw
.state
&WIN_VISIBLE
) == 0) {
4482 //dlogf("invisible");
4483 lastDrawTime
= term
->lastDrawTime
= 1;
4484 term
->wantRedraw
= 1;
4488 if (y1
== 0 && y2
== term
->row
) {
4490 for (int y
= 0; y
< y2
; ++y
) if (!(term
->dirty
[y
]&0x02)) { fulldirty
= 0; break; }
4493 if (term
->topline
< term
->row
) {
4494 for (int y
= y1
; y
< y2
; ++y
) drawline(x1
, x2
, y
+term
->topline
, y
, fulldirty
);
4496 if (term
->topline
> 0) {
4497 int scry
= MIN(term
->topline
, term
->row
), y
= term
->row
;
4500 if (term
->topline
>= term
->row
) y
+= term
->topline
-term
->row
;
4501 while (--scry
>= 0) {
4502 drawline(0, term
->col
, scry
, y
, 0);
4506 if (fulldirty
) xcopy(0, 0, term
->col
, term
->row
);
4510 lastDrawTime
= term
->lastDrawTime
= mclock_ticks();
4511 term
->wantRedraw
= 0;
4515 static void draw (int forced
) {
4517 //fprintf(stderr, "draw(%d) (%d)\n", forced, mclock_ticks());
4518 drawregion(0, 0, term
->col
, term
->row
, forced
);
4524 static int xisyinunused (int y) {
4525 if (xw.tabheight > 0 && term != NULL) {
4526 int unh = xw.h-(term->row*xw.ch)-xw.tabheight;
4529 switch (opt_tabposition) {
4531 return (y >= xw.h-xw.tabheight-unh && y < xw.h-xw.tabheight);
4534 return (y >= xw.tabheight+(term->row*xw.ch)t);
4544 static void xclearunused (void) {
4545 if (xw
.tabheight
> 0 && term
!= NULL
) {
4546 int unh
= xw
.h
-(term
->row
*xw
.ch
)-xw
.tabheight
;
4549 switch (opt_tabposition
) {
4551 XClearArea(xw
.dpy
, xw
.win
, 0, xw
.h
-xw
.tabheight
-unh
, xw
.w
, unh
, False
);
4554 XClearArea(xw
.dpy
, xw
.win
, 0, xw
.tabheight
+(term
->row
*xw
.ch
), xw
.w
, unh
, False
);
4562 static void expose (XEvent
*ev
) {
4563 XExposeEvent
*e
= &ev
->xexpose
;
4565 if (xw
.state
&WIN_REDRAW
) {
4566 if (!e
->count
&& term
!= NULL
) {
4567 xw
.state
&= ~WIN_REDRAW
;
4568 xcopy(0, 0, term
->col
, term
->row
);
4572 } else if (term
!= NULL
) {
4573 int taby
= (opt_tabposition
==1 ? 0 : xw
.h
-xw
.tabheight
);
4574 int termy
= (opt_tabposition
==1 ? xw
.tabheight
: 0);
4575 int x0
= e
->x
/xw
.cw
, y0
= (e
->y
-termy
)/xw
.ch
;
4576 int x1
= (e
->x
+e
->width
)/xw
.cw
, y1
= (e
->y
-termy
+e
->height
)/xw
.ch
;
4578 //fprintf(stderr, "x=%d; y=%d; w=%d; h=%d\n", e->x, e->y, e->width, e->height);
4579 //fprintf(stderr, "taby=%d; tabh=%d\n", taby, xw.tabheight);
4580 //fprintf(stderr, "x0=%d; y0=%d; x1=%d; y1=%d\n", x0, y0, x1, y1);
4582 if (e
->y
<= taby
+xw
.tabheight
&& e
->y
+e
->height
>= taby
) {
4583 //fprintf(stderr, "tabbar!\n");
4586 //XCopyArea(xw.dpy, term->picbuf, xw.win, dc.gc, e->x, e->y, e->width, e->height, e->x, e->y+(opt_tabposition==1?xw.height:0)));
4587 //xcopy(0, 0, term->col, term->row);
4588 //xclear(x0, y0, x1-x0+1, y1-y0+1);
4589 LIMIT(x0
, 0, term
->col
-1);
4590 LIMIT(x1
, 0, term
->col
-1);
4591 LIMIT(y0
, 0, term
->row
-1);
4592 LIMIT(y1
, 0, term
->row
-1);
4593 //fprintf(stderr, "*:x0=%d; y0=%d; x1=%d; y1=%d\n", x0, y0, x1, y1);
4594 xcopy(x0
, y0
, x1
-x0
+1, y1
-y0
+1);
4595 //xclearunused(); //FIXME: optimize this
4604 static void visibility (XEvent
*ev
) {
4605 XVisibilityEvent
*e
= &ev
->xvisibility
;
4607 if (e
->state
== VisibilityFullyObscured
) xw
.state
&= ~WIN_VISIBLE
;
4608 else if ((xw
.state
&WIN_VISIBLE
) == 0) xw
.state
|= WIN_VISIBLE
| WIN_REDRAW
; /* need a full redraw for next Expose, not just a buf copy */
4612 static void unmap (XEvent
*ev
) {
4613 xw
.state
&= ~WIN_VISIBLE
;
4617 static void xseturgency (int add
) {
4618 XWMHints
*h
= XGetWMHints(xw
.dpy
, xw
.win
);
4620 h
->flags
= add
? (h
->flags
| XUrgencyHint
) : (h
->flags
& ~XUrgencyHint
);
4621 XSetWMHints(xw
.dpy
, xw
.win
, h
);
4626 static void focus (XEvent
*ev
) {
4627 if (ev
->type
== FocusIn
) {
4628 xw
.state
|= WIN_FOCUSED
;
4632 xw
.state
&= ~WIN_FOCUSED
;
4639 //xcopy(0, 0, term->col, term->row);
4643 ////////////////////////////////////////////////////////////////////////////////
4645 static const char *kmap (KeySym k
, uint state
) {
4646 const char *res
= NULL
;
4648 state
&= ~Mod2Mask
; // numlock
4649 for (int f
= 0; f
< keymap_used
; ++f
) {
4650 uint mask
= keymap
[f
].mask
;
4652 if (keymap
[f
].key
== k
&& ((mask
== XK_NO_MOD
&& !state
) || state
== mask
)) {
4653 //fprintf(stderr, "kp: %d\n", IS_SET(MODE_APPKEYPAD));
4654 if (!IS_SET(MODE_APPKEYPAD
)) {
4655 if (!keymap
[f
].kp
) {
4656 //fprintf(stderr, "nokp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4657 return keymap
[f
].str
; // non-keypad hit
4661 //fprintf(stderr, "kp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4662 if (keymap
[f
].kp
) return keymap
[f
].str
; // keypad hit
4663 res
= keymap
[f
].str
; // kp mode, but non-kp mapping found
4670 static const char *kbind (KeySym k
, uint state
) {
4671 state
&= ~Mod2Mask
; // numlock
4672 for (int f
= 0; f
< keybinds_used
; ++f
) {
4673 uint mask
= keybinds
[f
].mask
;
4675 if (keybinds
[f
].key
== k
&& ((mask
== XK_NO_MOD
&& !state
) || state
== mask
)) return keybinds
[f
].str
;
4681 static KeySym
do_keytrans (KeySym ks
) {
4682 for (int f
= 0; f
< keytrans_used
; ++f
) if (keytrans
[f
].src
== ks
) return keytrans
[f
].dst
;
4687 static void cmdline_closequeryexec (int cancelled
) {
4689 char *rep
= term
->cmdline
+term
->cmdreslen
;
4692 if (!rep
[0] || strchr("yt", tolower(rep
[0])) != NULL
) {
4693 closeRequestComes
= 2;
4697 closeRequestComes
= 0;
4701 static void kpress (XEvent
*ev
) {
4702 XKeyEvent
*e
= &ev
->xkey
;
4703 KeySym ksym
= NoSymbol
;
4709 if (term
== NULL
) return;
4711 if (!ptrBlanked
&& opt_ptrblank
> 0) xblankPointer();
4713 //if (len < 1) len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
4714 if ((len
= Xutf8LookupString(xw
.xic
, e
, buf
, sizeof(buf
), &ksym
, &status
)) > 0) buf
[len
] = 0;
4715 // leave only known mods
4716 e
->state
&= (Mod1Mask
| Mod4Mask
| ControlMask
| ShiftMask
);
4719 const char *ksname
= XKeysymToString(ksym
);
4721 fprintf(stderr
, "utf(%d):[%s] (%s) 0x%08x\n", len
, len
>=0?buf
:"<shit>", ksname
, (unsigned int)e
->state
);
4724 if ((kstr
= kbind(ksym
, e
->state
)) != NULL
) {
4726 executeCommands(kstr
);
4730 if (term
->cmdMode
!= CMDMODE_NONE
) {
4731 int mode
= term
->cmdMode
;
4733 switch (do_keytrans(ksym
)) {
4736 if (term
->cmdexecfn
) term
->cmdexecfn(0);
4737 else if (mode
== CMDMODE_INPUT
) executeCommands(term
->cmdline
);
4740 if (mode
== CMDMODE_INPUT
) {
4742 term
->cmdtabpos
= -1;
4743 term
->cmdprevc
= NULL
;
4746 if (term
->cmdexecfn
) term
->cmdexecfn(1);
4751 if (term
->cmdexecfn
) term
->cmdexecfn(1);
4754 if (mode
== CMDMODE_INPUT
&& term
->cmdline
[0] && term
->cmdcl
== 0 && term
->cmdexecfn
== NULL
) {
4757 if (term
->cmdtabpos
< 0) {
4758 term
->cmdtabpos
= 0;
4759 while (term
->cmdline
[term
->cmdtabpos
] && isalnum(term
->cmdline
[term
->cmdtabpos
])) ++term
->cmdtabpos
;
4760 if (term
->cmdline
[term
->cmdtabpos
]) {
4761 term
->cmdtabpos
= -1;
4764 term
->cmdprevc
= NULL
;
4766 cpl
= findCommandCompletion(term
->cmdline
, term
->cmdtabpos
, term
->cmdprevc
);
4767 if (cpl
== NULL
&& term
->cmdprevc
!= NULL
) cpl
= findCommandCompletion(term
->cmdline
, term
->cmdtabpos
, NULL
);
4768 term
->cmdprevc
= cpl
;
4769 if (cpl
!= NULL
) strcpy(term
->cmdline
, cpl
);
4771 } else if (mode
!= CMDMODE_INPUT
) {
4773 if (term
->cmdexecfn
) term
->cmdexecfn(1);
4777 if (mode
!= CMDMODE_INPUT
) {
4783 if (mode
== CMDMODE_INPUT
) {
4784 if (len
> 0 && (unsigned char)buf
[0] >= 32) {
4786 term
->cmdtabpos
= -1;
4787 term
->cmdprevc
= NULL
;
4795 if ((kstr
= kmap(do_keytrans(ksym
), e
->state
)) != NULL
) {
4801 int meta
= (e
->state
&Mod1Mask
);
4803 int shift = (e->state&ShiftMask);
4804 int ctrl = (e->state&ControlMask);
4810 ttywritestr("\x1b\x0a");
4812 if (IS_SET(MODE_CRLF
)) ttywritestr("\r\n"); else ttywritestr("\r");
4818 if (meta
&& len
== 1) ttywritestr("\x1b");
4827 ////////////////////////////////////////////////////////////////////////////////
4829 static void cmessage (XEvent
*e
) {
4830 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
4831 if (e
->xclient
.message_type
== xw
.xembed
&& e
->xclient
.format
== 32) {
4832 if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_IN
) {
4833 xw
.state
|= WIN_FOCUSED
;
4836 } else if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_OUT
) {
4837 xw
.state
&= ~WIN_FOCUSED
;
4842 xcopy(0, 0, term
->col
, term
->row
);
4846 if (e
->xclient
.data
.l
[0] == XA_WM_DELETE_WINDOW
) {
4847 closeRequestComes
= 1;
4853 ////////////////////////////////////////////////////////////////////////////////
4854 static void resize (XEvent
*e
) {
4858 //if (e->xconfigure.width == 65535 || e->xconfigure.width == -1) e->xconfigure.width = xw.w;
4859 if (e
->xconfigure
.height
== 65535 || e
->xconfigure
.height
== -1) e
->xconfigure
.height
= xw
.h
;
4860 //if ((short int)e->xconfigure.height < xw.ch) return;
4862 if (e
->xconfigure
.width
== xw
.w
&& e
->xconfigure
.height
== xw
.h
) return;
4863 xw
.w
= e
->xconfigure
.width
;
4864 xw
.h
= e
->xconfigure
.height
;
4866 row
= (xw
.h
-xw
.tabheight
)/xw
.ch
;
4867 //fprintf(stderr, "neww=%d; newh=%d; ch=%d; th=%d; col=%d; row=%d; ocol=%d; orow=%d\n", xw.w, xw.h, xw.ch, xw.tabheight, col, row, term->col, term->row);
4868 if (col
== term
->col
&& row
== term
->row
) return;
4869 for (int f
= 0; f
< term_count
; ++f
) {
4870 term
= term_array
[f
];
4871 if (tresize(col
, row
) && ot
== term
) draw(1);
4876 XFreePixmap(xw
.dpy
, xw
.pictab
);
4877 xw
.pictab
= XCreatePixmap(xw
.dpy
, xw
.win
, xw
.w
, xw
.tabheight
>0?xw
.tabheight
:1, XDefaultDepth(xw
.dpy
, xw
.scr
));
4882 static inline int last_draw_too_old (void) {
4883 int tt
= mclock_ticks();
4886 if (term
->dead
|| !term
->wantRedraw
) return 0;
4887 return (tt
-term
->lastDrawTime
>= opt_drawtimeout
);
4889 return (tt
-lastDrawTime
>= opt_maxdrawtimeout
);
4893 ////////////////////////////////////////////////////////////////////////////////
4895 static void (*handler
[LASTEvent
])(XEvent
*) = {
4896 [KeyPress
] = kpress
,
4897 [ClientMessage
] = cmessage
,
4898 [ConfigureNotify
] = resize
,
4899 [VisibilityNotify
] = visibility
,
4900 [UnmapNotify
] = unmap
,
4904 [MotionNotify
] = bmotion
,
4905 [ButtonPress
] = bpress
,
4906 [ButtonRelease
] = brelease
,
4907 [SelectionNotify
] = selnotify
,
4908 [SelectionRequest
] = selrequest
,
4909 [SelectionClear
] = selclear
,
4913 static void run (void) {
4914 //int stuff_to_print = 0;
4915 int xfd
= XConnectionNumber(xw
.dpy
);
4917 ptrLastMove
= mclock_ticks();
4918 while (term_count
> 0) {
4921 struct timeval timeout
;
4929 //FD_SET(term->cmdfd, &rfd);
4930 // have something to write?
4931 for (int f
= 0; f
< term_count
; ++f
) {
4932 Term
*t
= term_array
[f
];
4934 if (!t
->dead
&& term
->cmdfd
>= 0 && t
->pid
!= 0) {
4935 if (t
->cmdfd
> maxfd
) maxfd
= t
->cmdfd
;
4936 FD_SET(t
->cmdfd
, &rfd
);
4937 if (t
->wrbufpos
< t
->wrbufused
) FD_SET(t
->cmdfd
, &wfd
);
4942 timeout
.tv_usec
= (opt_drawtimeout
+2)*1000;
4943 if (select(maxfd
+1, &rfd
, &wfd
, NULL
, &timeout
) < 0) {
4944 if (errno
== EINTR
) continue;
4945 die("select failed: %s", SERRNO
);
4949 for (int f
= 0; f
< term_count
; ++f
) {
4950 Term
*t
= term_array
[f
];
4952 if (!t
->dead
&& term
->cmdfd
>= 0 && term
->pid
!= 0) {
4954 if (FD_ISSET(t
->cmdfd
, &wfd
)) ttyflushwrbuf();
4955 if (FD_ISSET(t
->cmdfd
, &rfd
)) ttyread(); //t->wantRedraw = 1;
4961 if (term_count
== 0) exit(exitcode
);
4964 if (term
!= NULL
&& term
->curblink
> 0) {
4965 int tt
= mclock_ticks();
4967 if (tt
-term
->lastBlinkTime
>= term
->curblink
) {
4968 term
->lastBlinkTime
= tt
;
4969 if ((xw
.state
&WIN_FOCUSED
) || term
->curblinkinactive
) {
4970 term
->curbhidden
= (term
->curbhidden
? 0 : -1);
4973 term
->curbhidden
= 0;
4977 if (updateTabBar
) xdrawTabBar();
4978 if (dodraw
|| last_draw_too_old()) draw(0);
4980 if (XPending(xw
.dpy
)) {
4981 while (XPending(xw
.dpy
)) {
4982 XNextEvent(xw
.dpy
, &ev
);
4984 //case VisibilityNotify:
4992 ptrLastMove
= mclock_ticks();
4996 if (XFilterEvent(&ev
, xw
.win
)) continue;
4997 if (handler
[ev
.type
]) (handler
[ev
.type
])(&ev
);
5002 switch (closeRequestComes
) {
5003 case 1: // just comes
5004 if (opt_ignoreclose
== 0) {
5006 tcmdlineinitex("Do you really want to close sterm [Y/n]? ");
5007 term
->cmdexecfn
= cmdline_closequeryexec
;
5008 } else if (opt_ignoreclose
< 0) {
5009 //FIXME: kill all clients?
5012 closeRequestComes
= 0;
5014 case 2: // ok, die now
5015 //FIXME: kill all clients?
5020 if (!ptrBlanked
&& opt_ptrblank
> 0 && mclock_ticks()-ptrLastMove
>= opt_ptrblank
) {
5027 ////////////////////////////////////////////////////////////////////////////////
5028 typedef const char * (*IniHandlerFn
) (const char *optname
, const char *fmt
, char *argstr
, void *udata
);
5039 static const char *inifnGenericOneArg (const char *optname
, const char *fmt
, char *argstr
, void *udata
) {
5040 return iniParseArguments(argstr
, fmt
, udata
);
5044 static const char *inifnGenericOneStr (const char *optname
, const char *fmt
, char *argstr
, void *udata
) {
5046 const char *err
= iniParseArguments(argstr
, fmt
, &s
);
5048 if (err
!= NULL
) return err
;
5049 if ((s
= strdup(s
)) == NULL
) return "out of memory";
5051 char **ustr
= (char **)udata
;
5053 if (*ustr
) free(*ustr
);
5060 static const char *inifnTabPosition (const char *optname
, const char *fmt
, char *argstr
, void *udata
) {
5062 const char *err
= NULL
;
5067 if ((err
= iniParseArguments(argstr
, "R-", &argstr
)) != NULL
) return err
;
5068 if (!argstr
[0]) break;
5070 if ((err
= iniParseArguments(argstr
, "s!-R-", &s
, &argstr
)) != NULL
) return err
;
5071 if (tolower(s
[0]) == 't') newpos
= 1;
5072 else if (tolower(s
[0]) == 'b') newpos
= 0;
5073 else if ((err
= iniParseArguments(s
, fmt
, &newpos
)) != NULL
) return err
;
5076 if (newpos
== -1) return "invalid tabbar position";
5077 opt_tabposition
= newpos
;
5082 static const IniCommand iniCommands
[] = {
5083 {"term", "s!-", &opt_term
, inifnGenericOneStr
},
5084 {"class", "s!-", &opt_class
, inifnGenericOneStr
},
5085 {"title", "s!-", &opt_title
, inifnGenericOneStr
},
5086 {"fontnorm", "s!-", &opt_fontnorm
, inifnGenericOneStr
},
5087 {"fontbold", "s!-", &opt_fontbold
, inifnGenericOneStr
},
5088 {"fonttab", "s!-", &opt_fonttab
, inifnGenericOneStr
},
5089 {"shell", "s!-", &opt_shell
, inifnGenericOneStr
},
5090 {"doubleclicktimeout", "i{0,10000}", &opt_doubleclick_timeout
, inifnGenericOneArg
},
5091 {"tripleclicktimeout", "i{0,10000}", &opt_tripleclick_timeout
, inifnGenericOneArg
},
5092 {"tabsize", "i{1,256}", &opt_tabsize
, inifnGenericOneArg
},
5093 {"defaultfg", "i{0,511}", &defaultFG
, inifnGenericOneArg
},
5094 {"defaultbg", "i{0,511}", &defaultBG
, inifnGenericOneArg
},
5095 {"defaultcursorfg", "i{0,511}", &defaultCursorFG
, inifnGenericOneArg
},
5096 {"defaultcursorbg", "i{0,511}", &defaultCursorBG
, inifnGenericOneArg
},
5097 {"defaultinactivecursorfg", "i{0,511}", &defaultCursorInactiveFG
, inifnGenericOneArg
},
5098 {"defaultinactivecursorbg", "i{-1,511}", &defaultCursorInactiveBG
, inifnGenericOneArg
},
5099 {"defaultboldfg", "i{-1,511}", &defaultBoldFG
, inifnGenericOneArg
},
5100 {"defaultunderlinefg", "i{-1,511}", &defaultUnderlineFG
, inifnGenericOneArg
},
5101 {"normaltabfg", "i{0,511}", &normalTabFG
, inifnGenericOneArg
},
5102 {"normaltabbg", "i{0,511}", &normalTabBG
, inifnGenericOneArg
},
5103 {"activetabfg", "i{0,511}", &activeTabFG
, inifnGenericOneArg
},
5104 {"activetabbg", "i{0,511}", &activeTabBG
, inifnGenericOneArg
},
5105 {"maxhistory", "i{0,65535}", &opt_maxhistory
, inifnGenericOneArg
},
5106 {"ptrblank", "i{0,65535}", &opt_ptrblank
, inifnGenericOneArg
},
5107 {"tabcount", "i{1,128}", &opt_tabcount
, inifnGenericOneArg
},
5108 {"tabposition", "i{0,1}", &opt_tabposition
, inifnTabPosition
},
5109 {"drawtimeout", "i{5,30000}", &opt_drawtimeout
, inifnGenericOneArg
},
5110 {"audiblebell", "b", &opt_audiblebell
, inifnGenericOneArg
},
5111 {"urgentbell", "b", &opt_urgentbell
, inifnGenericOneArg
},
5112 {"cursorblink", "i{0,10000}", &opt_cursorBlink
, inifnGenericOneArg
},
5113 {"cursorblinkinactive", "b", &opt_cursorBlinkInactive
, inifnGenericOneArg
},
5114 {"drawunderline", "b", &opt_drawunderline
, inifnGenericOneArg
},
5115 {"ignoreclose", "i{-1,1}", &opt_ignoreclose
, inifnGenericOneArg
},
5116 {"maxdrawtimeout", "i{100,60000}", &opt_maxdrawtimeout
, inifnGenericOneArg
},
5117 {"fastredraw", "b", &opt_fastredraw
, inifnGenericOneArg
},
5118 {NULL
, NULL
, NULL
, NULL
}
5122 #define MISC_CMD_NONE ((const char *)-1)
5125 // NULL: command processed; MISC_CMD_NONE: unknown command; !NULL: error
5126 static const char *processMiscCmds (const char *optname
, char *argstr
) {
5127 const char *err
= NULL
;
5129 if (strcasecmp(optname
, "unimap") == 0) {
5133 if (iniParseArguments(argstr
, "R!-", &argstr
) == NULL
) {
5134 if (strcasecmp(argstr
, "reset") == 0 || strcasecmp(argstr
, "clear") == 0) {
5135 if (unimap
) free(unimap
);
5140 //unimap 0x2592 0x61 alt
5141 if ((err
= iniParseArguments(argstr
, "i{0,65535}i{0,255}|s!-", &uni
, &ch
, &alt
)) != NULL
) return err
;
5142 if (alt
!= NULL
&& strcasecmp(alt
, "alt") != 0) return "invalid unimap";
5143 if (unimap
== NULL
) {
5144 if ((unimap
= calloc(65536, sizeof(unimap
[0]))) == NULL
) return "out of memory";
5146 if (alt
!= NULL
&& ch
== 0) alt
= NULL
;
5147 if (alt
!= NULL
&& ch
< 96) return "invalid unimap";
5149 if (alt
!= NULL
) unimap
[uni
] |= 0x8000;
5153 if (strcasecmp(optname
, "keytrans") == 0) {
5154 char *src
= NULL
, *dst
= NULL
;
5156 if (iniParseArguments(argstr
, "R!-", &argstr
) == NULL
) {
5157 if (strcasecmp(argstr
, "reset") == 0 || strcasecmp(argstr
, "clear") == 0) {
5162 if ((err
= iniParseArguments(argstr
, "s!-s!-", &src
, &dst
)) != NULL
) return err
;
5163 keytrans_add(src
, dst
);
5167 if (strcasecmp(optname
, "keybind") == 0) {
5168 char *key
= NULL
, *act
= NULL
;
5169 if (iniParseArguments(argstr
, "R!-", &argstr
) == NULL
) {
5170 if (strcasecmp(argstr
, "reset") == 0 || strcasecmp(argstr
, "clear") == 0) {
5175 if ((err
= iniParseArguments(argstr
, "s!-R!", &key
, &act
)) != NULL
) return err
;
5176 if (act
[0] == '"') {
5179 if ((err
= iniParseArguments(act
, "s!", &t
)) != NULL
) return err
;
5182 keybind_add(key
, act
);
5186 if (strcasecmp(optname
, "keymap") == 0) {
5187 char *key
= NULL
, *str
= NULL
;
5189 if (iniParseArguments(argstr
, "R!-", &argstr
) == NULL
) {
5190 if (strcasecmp(argstr
, "reset") == 0 || strcasecmp(argstr
, "clear") == 0) {
5195 if ((err
= iniParseArguments(argstr
, "s!-s!-", &key
, &str
)) != NULL
) return err
;
5196 keymap_add(key
, str
);
5200 return MISC_CMD_NONE
;
5204 #define INI_LINE_SIZE (32768)
5206 // <0: file not found
5207 // >0: file loading error
5209 static int loadConfig (const char *fname
) {
5210 int inifelse
= 0; // 0: not; 1: doing true; 2: doing false; -1: waiting else/endif; -2: waiting endif
5211 FILE *fi
= fopen(fname
, "r");
5212 const char *err
= NULL
;
5216 if (fi
== NULL
) return -1;
5217 if ((line
= malloc(INI_LINE_SIZE
)) == NULL
) { err
= "out of memory"; goto quit
; }
5219 while (fgets(line
, INI_LINE_SIZE
-1, fi
) != NULL
) {
5220 char *optname
, *argstr
, *d
;
5224 line
[INI_LINE_SIZE
-1] = 0;
5226 for (optname
= line
; *optname
&& isspace(*optname
); ++optname
) ;
5227 if (!optname
[0] || optname
[0] == '#') continue; // comment
5228 if (!isalnum(optname
[0])) { err
= "invalid option name"; goto quit
; }
5229 d
= argstr
= optname
;
5231 if (!argstr
[0] || isspace(argstr
[0])) break;
5232 if (!isalnum(argstr
[0]) && argstr
[0] != '_' && argstr
[0] != '.') { err
= "invalid option name"; goto quit
; }
5233 if (argstr
[0] != '_') *d
++ = tolower(*argstr
);
5236 if (*argstr
) ++argstr
;
5238 if (!isalnum(optname
[0])) { err
= "invalid option name"; goto quit
; }
5240 if (strcasecmp(optname
, "ifterm") == 0) {
5243 if (inifelse
!= 0) { err
= "nested ifs are not allowed"; goto quit
; }
5245 if ((err
= iniParseArguments(argstr
, "s", &val
)) != NULL
) goto quit
;
5246 if (strcasecmp(opt_term
, val
) == 0) inifelse
= 1;
5249 if (strcasecmp(optname
, "else") == 0) {
5251 case -1: inifelse
= 2; break;
5252 case 2: case -2: case 0: err
= "else without if"; goto quit
;
5253 case 1: inifelse
= -2; break;
5257 if (strcasecmp(optname
, "endif") == 0) {
5259 case -1: case -2: case 1: case 2: inifelse
= 0; break;
5260 case 0: err
= "endif without if"; goto quit
;
5267 //fprintf(stderr, "skip: [%s]\n", argstr);
5270 if (opt_term_locked
&& strcasecmp(optname
, "term") == 0) continue; // termname given in command line
5271 // ok, we have option name in `optname` and arguments in `argstr`
5272 if (strncmp(optname
, "color.", 6) == 0) {
5277 if (!optname
[0]) { err
= "invalid color option"; goto quit
; }
5279 if (!isdigit(*optname
)) { err
= "invalid color option"; goto quit
; }
5280 n
= (n
*10)+(optname
[0]-'0');
5283 if (n
< 0 || n
> 511) { err
= "invalid color index"; goto quit
; }
5285 if ((err
= iniParseArguments(argstr
, "s!-", &s
)) != NULL
) goto quit
;
5286 if ((s
= strdup(s
)) == NULL
) { err
= "out of memory"; goto quit
; }
5287 if (opt_colornames
[n
] != NULL
) free(opt_colornames
[n
]);
5288 opt_colornames
[n
] = s
;
5292 if ((err
= processMiscCmds(optname
, argstr
)) != MISC_CMD_NONE
) {
5293 if (err
!= NULL
) goto quit
;
5299 for (int f
= 0; iniCommands
[f
].name
!= NULL
; ++f
) {
5300 if (strcmp(iniCommands
[f
].name
, optname
) == 0) {
5301 if ((err
= iniCommands
[f
].fn(optname
, iniCommands
[f
].fmt
, argstr
, iniCommands
[f
].udata
)) != NULL
) goto quit
;
5307 fprintf(stderr
, "ini error at line %d: unknown option '%s'!\n", lineno
, optname
);
5311 if (line
!= NULL
) free(line
);
5313 if (err
== NULL
&& inifelse
!= 0) err
= "if without endif";
5314 if (err
!= NULL
) die("ini error at line %d: %s", lineno
, err
);
5319 static void initDefaultOptions (void) {
5320 opt_title
= strdup("sterm");
5321 opt_class
= strdup("sterm");
5322 opt_term
= strdup(TNAME
);
5323 opt_fontnorm
= strdup(FONT
);
5324 opt_fontbold
= strdup(FONTBOLD
);
5325 opt_fonttab
= strdup(FONTTAB
);
5326 opt_shell
= strdup(SHELL
);
5328 memset(opt_colornames
, 0, sizeof(opt_colornames
));
5329 for (int f
= 0; f
< LEN(defcolornames
); ++f
) opt_colornames
[f
] = strdup(defcolornames
[f
]);
5330 for (int f
= 0; f
< LEN(defextcolornames
); ++f
) opt_colornames
[f
+256] = strdup(defextcolornames
[f
]);
5332 keytrans_add("KP_Home", "Home");
5333 keytrans_add("KP_Left", "Left");
5334 keytrans_add("KP_Up", "Up");
5335 keytrans_add("KP_Right", "Right");
5336 keytrans_add("KP_Down", "Down");
5337 keytrans_add("KP_Prior", "Prior");
5338 keytrans_add("KP_Next", "Next");
5339 keytrans_add("KP_End", "End");
5340 keytrans_add("KP_Begin", "Begin");
5341 keytrans_add("KP_Insert", "Insert");
5342 keytrans_add("KP_Delete", "Delete");
5344 keybind_add("shift+Insert", "PastePrimary");
5345 keybind_add("alt+Insert", "PasteCliboard");
5346 keybind_add("ctrl+alt+t", "NewTab");
5347 keybind_add("ctrl+alt+Left", "SwitchToTab prev");
5348 keybind_add("ctrl+alt+Right", "SwitchToTab next");
5350 keymap_add("BackSpace", "\177");
5351 keymap_add("Insert", "\x1b[2~");
5352 keymap_add("Delete", "\x1b[3~");
5353 keymap_add("Home", "\x1b[1~");
5354 keymap_add("End", "\x1b[4~");
5355 keymap_add("Prior", "\x1b[5~");
5356 keymap_add("Next", "\x1b[6~");
5357 keymap_add("F1", "\x1bOP");
5358 keymap_add("F2", "\x1bOQ");
5359 keymap_add("F3", "\x1bOR");
5360 keymap_add("F4", "\x1bOS");
5361 keymap_add("F5", "\x1b[15~");
5362 keymap_add("F6", "\x1b[17~");
5363 keymap_add("F7", "\x1b[18~");
5364 keymap_add("F8", "\x1b[19~");
5365 keymap_add("F9", "\x1b[20~");
5366 keymap_add("F10", "\x1b[21~");
5367 keymap_add("Up", "\x1bOA");
5368 keymap_add("Down", "\x1bOB");
5369 keymap_add("Right", "\x1bOC");
5370 keymap_add("Left", "\x1bOD");
5374 ////////////////////////////////////////////////////////////////////////////////
5375 static Term
*oldTerm
;
5376 static int oldTermIdx
;
5377 static Term
*newTerm
;
5378 static int newTermIdx
;
5379 static int newTermSwitch
;
5382 typedef void (*CmdHandlerFn
) (const char *cmdname
, char *argstr
);
5390 static void cmdPastePrimary (const char *cmdname
, char *argstr
) {
5391 selpaste(XA_PRIMARY
);
5395 static void cmdPasteSecondary (const char *cmdname
, char *argstr
) {
5396 selpaste(XA_SECONDARY
);
5400 static void cmdPasteClipboard (const char *cmdname
, char *argstr
) {
5401 selpaste(XA_CLIPBOARD
);
5405 static void cmdExec (const char *cmdname
, char *argstr
) {
5407 if (term
->execcmd
!= NULL
) free(term
->execcmd
);
5408 term
->execcmd
= (argstr
[0] ? strdup(argstr
) : NULL
);
5413 static int parseTabArgs (char *argstr
, int *noswitch
, int nowrap
, int idx
) {
5417 while (*argstr
&& isspace(*argstr
)) ++argstr
;
5418 if (!argstr
[0]) break;
5419 if (iniParseArguments(argstr
, "s-R-", &arg
, &argstr
) != NULL
) break;
5421 if (strcasecmp(arg
, "noswitch") == 0) *noswitch
= 1;
5422 else if (strcasecmp(arg
, "switch") == 0) *noswitch
= 0;
5423 else if (strcasecmp(arg
, "nowrap") == 0) nowrap
= 1;
5424 else if (strcasecmp(arg
, "wrap") == 0) nowrap
= 0;
5425 else if (strcasecmp(arg
, "first") == 0) idx
= 0;
5426 else if (strcasecmp(arg
, "last") == 0) idx
= term_count
-1;
5427 else if (strcasecmp(arg
, "prev") == 0) idx
= -1;
5428 else if (strcasecmp(arg
, "next") == 0) idx
= -2;
5433 n
= strtol(arg
, &eptr
, 0);
5434 if (!eptr
[0] && n
>= 0 && n
< term_count
) idx
= n
;
5439 if ((idx
= termidx
-1) < 0) idx
= nowrap
? 0 : term_count
-1;
5442 if ((idx
= termidx
+1) >= term_count
) idx
= nowrap
? term_count
-1 : 0;
5449 static void flushNewTerm (void) {
5450 if (newTerm
!= NULL
) {
5451 if (newTermSwitch
&& term
!= NULL
) term
->lastActiveTime
= mclock_ticks();
5453 termidx
= newTermIdx
;
5454 tinitialize(term_array
[0]->col
, term_array
[0]->row
);
5456 term
->picbufh
= term
->row
*xw
.ch
;
5457 term
->picbufw
= term
->col
*xw
.cw
;
5458 term
->picbuf
= XCreatePixmap(xw
.dpy
, xw
.win
, term
->picbufw
, term
->picbufh
, XDefaultDepth(xw
.dpy
, xw
.scr
));
5459 XFillRectangle(xw
.dpy
, term
->picbuf
, dc
.gc
, 0, 0, term
->picbufw
, term
->picbufh
);
5461 if (ttynew(term
) != 0) {
5463 termidx
= oldTermIdx
;
5464 termfree(newTermIdx
);
5468 if (newTermSwitch
) {
5471 switchToTerm(newTermIdx
, 1);
5473 oldTermIdx
= termidx
;
5476 termidx
= oldTermIdx
;
5484 static void cmdNewTab (const char *cmdname
, char *argstr
) {
5485 int noswitch
= 0, idx
;
5487 if (opt_disabletabs
) return;
5489 if ((newTerm
= termalloc()) == NULL
) return;
5490 /*idx =*/ parseTabArgs(argstr
, &noswitch
, 0, termidx
);
5493 if (term
!= NULL
) term
->lastActiveTime
= mclock_ticks();
5496 newTermIdx
= termidx
= idx
;
5498 newTermSwitch
= !noswitch
;
5502 static void cmdCloseTab (const char *cmdname
, char *argstr
) {
5504 if (term
!= NULL
&& !term
->dead
) kill(term
->pid
, SIGTERM
);
5508 static void cmdKillTab (const char *cmdname
, char *argstr
) {
5510 if (!term
->dead
) kill(term
->pid
, SIGKILL
);
5514 static void cmdSwitchToTab (const char *cmdname
, char *argstr
) {
5515 int noswitch
= 0, idx
;
5518 idx
= parseTabArgs(argstr
, &noswitch
, 0, -666);
5520 switchToTerm(idx
, 1);
5522 oldTermIdx
= termidx
;
5527 static void cmdMoveTabTo (const char *cmdname
, char *argstr
) {
5528 int noswitch
= 0, idx
;
5531 idx
= parseTabArgs(argstr
, &noswitch
, 0, termidx
);
5532 if (idx
!= termidx
&& idx
>= 0 && idx
< term_count
) {
5533 Term
*t
= term_array
[termidx
];
5535 // remove current term
5536 for (int f
= termidx
+1; f
< term_count
; ++f
) term_array
[f
-1] = term_array
[f
];
5538 for (int f
= term_count
-2; f
>= idx
; --f
) term_array
[f
+1] = term_array
[f
];
5539 term_array
[idx
] = t
;
5548 static void cmdDefaultFG (const char *cmdname
, char *argstr
) {
5552 if (iniParseArguments(argstr
, "i{0,511}|s-", &c
, &s
) == NULL
) {
5553 if (s
!= NULL
&& tolower(s
[0]) == 'g') defaultFG
= c
; else term
->deffg
= c
;
5558 static void cmdDefaultBG (const char *cmdname
, char *argstr
) {
5562 if (iniParseArguments(argstr
, "i{0,511}|s-", &c
) == NULL
) {
5563 if (s
!= NULL
&& tolower(s
[0]) == 'g') {
5567 XSetWindowBackground(xw
.dpy
, xw
.win
, getColor(term
->defbg
));
5568 if (newTerm
== NULL
) {
5578 static void scrollHistory (int delta
) {
5579 if (term
->maxhistory
< 1) return; // no history
5580 term
->topline
+= delta
;
5581 if (term
->topline
> term
->maxhistory
) term
->topline
= term
->maxhistory
;
5582 if (term
->topline
< 0) term
->topline
= 0;
5588 static void cmdScrollHistoryLineUp (const char *cmdname
, char *argstr
) {
5593 static void cmdScrollHistoryPageUp (const char *cmdname
, char *argstr
) {
5594 scrollHistory(term
->row
);
5598 static void cmdScrollHistoryLineDown (const char *cmdname
, char *argstr
) {
5603 static void cmdScrollHistoryPageDown (const char *cmdname
, char *argstr
) {
5604 scrollHistory(-term
->row
);
5608 static void cmdScrollHistoryTop (const char *cmdname
, char *argstr
) {
5609 scrollHistory(term
->linecount
);
5613 static void cmdScrollHistoryBottom (const char *cmdname
, char *argstr
) {
5614 scrollHistory(-term
->linecount
);
5618 static void cmdCommandMode (const char *cmdname
, char *argstr
) {
5620 if (term
->cmdMode
== CMDMODE_NONE
) tcmdlineinit(); else tcmdlinehide();
5626 static void cmdCursor (const char *cmdname
, char *argstr
) {
5630 if (iniParseArguments(argstr
, "s!-", &s
) != NULL
) {
5631 tcmdlinemsgf("cursor is %s", ((term
->c
.state
&CURSOR_HIDE
) ? "hidden" : "visible"));
5633 if (strcasecmp(s
, "show") == 0) term
->c
.state
&= ~CURSOR_HIDE
;
5634 else if (strcasecmp(s
, "hide") == 0) term
->c
.state
|= CURSOR_HIDE
;
5635 term
->wantRedraw
= 1;
5641 static void cmdResetAttrs (const char *cmdname
, char *argstr
) {
5643 term
->c
.attr
.attr
&= ~(ATTR_REVERSE
| ATTR_UNDERLINE
| ATTR_BOLD
);
5644 term
->c
.attr
.attr
|= ATTR_DEFFG
| ATTR_DEFBG
;
5645 term
->c
.attr
.fg
= term
->deffg
;
5646 term
->c
.attr
.bg
= term
->defbg
;
5651 static void cmdResetCharset (const char *cmdname
, char *argstr
) {
5653 term
->mode
&= ~(MODE_GFX0
|MODE_GFX1
);
5654 term
->charset
= MODE_GFX0
;
5660 static void cmdScreen (const char *cmdname
, char *argstr
) {
5664 if (iniParseArguments(argstr
, "s!-", &s
) != NULL
) {
5665 tcmdlinemsgf("screen: %s", (IS_SET(MODE_ALTSCREEN
) ? "alt" : "norm"));
5667 if (strcasecmp(s
, "norm") == 0) {
5668 if (IS_SET(MODE_ALTSCREEN
)) tswapscreen();
5669 } else if (strcasecmp(s
, "alt") == 0) {
5670 if (!IS_SET(MODE_ALTSCREEN
)) tswapscreen();
5678 static void cmdMouseReports (const char *cmdname
, char *argstr
) {
5682 if (iniParseArguments(argstr
, "b", &b
) != NULL
) {
5683 tcmdlinemsgf("mouse reports are o%s", (IS_SET(MODE_MOUSE
) ? "n" : "ff"));
5685 if (b
) term
->mode
|= MODE_MOUSEBTN
; else term
->mode
&= ~MODE_MOUSEBTN
;
5691 static int cmd_parseIntArg (const char *fmt
, char *argstr
, int *b
, int *global
, int *toggle
) {
5695 if (iniParseArguments(argstr
, "R-", &argstr
) != NULL
) break;
5696 if (!argstr
[0]) break;
5698 if (iniParseArguments(argstr
, "s!-R-", &s
, &argstr
) != NULL
) break;
5699 if (global
&& tolower(s
[0]) == 'g') {
5701 } else if (toggle
&& tolower(s
[0]) == 't') {
5704 if (!b
|| iniParseArguments(s
, fmt
, b
) != NULL
) return -1;
5711 static void cmdUTF8Locale (const char *cmdname
, char *argstr
) {
5712 int b
= -1, toggle
= 0;
5714 if (cmd_parseIntArg("b", argstr
, &b
, NULL
, &toggle
) != 0) return;
5716 tcmdlinemsgf("UTF8Locale: %s", ((needConversion
? !term
->needConv
: 1) ? "yes" : "no"));
5718 if (!needConversion
) b
= 1;
5719 if (term
!= NULL
) term
->needConv
= !b
;
5724 static void cmdAudibleBell (const char *cmdname
, char *argstr
) {
5725 int b
= -1, toggle
= 0, global
= 0;
5727 if (term
== NULL
) return;
5728 if (cmd_parseIntArg("b", argstr
, &b
, &global
, &toggle
) != 0) return;
5731 tcmdlinemsgf("AudibleBell: %s", (global
? opt_audiblebell
: (term
->belltype
&BELL_AUDIO
)) ? "yes" : "no");
5734 if (global
) opt_audiblebell
= !opt_audiblebell
; else term
->belltype
^= BELL_AUDIO
;
5736 if (global
) opt_audiblebell
= b
; else term
->belltype
= (term
->belltype
&~BELL_AUDIO
)|(b
!=0?BELL_AUDIO
:0);
5742 static void cmdUrgentBell (const char *cmdname
, char *argstr
) {
5743 int b
= -1, toggle
= 0, global
= 0;
5745 if (term
== NULL
) return;
5746 if (cmd_parseIntArg("b", argstr
, &b
, &global
, &toggle
) != 0) return;
5749 tcmdlinemsgf("UrgentBell: %s", (global
? opt_urgentbell
: (term
->belltype
&BELL_URGENT
)) ? "yes" : "no");
5752 if (global
) opt_urgentbell
= !opt_urgentbell
; else term
->belltype
^= BELL_URGENT
;
5754 if (global
) opt_urgentbell
= b
; else term
->belltype
= (term
->belltype
&~BELL_URGENT
)|(b
!=0?BELL_URGENT
:0);
5760 static void cmdMonochrome (const char *cmdname
, char *argstr
) {
5761 int b
= -1, global
= 0;
5763 if (term
== NULL
) return;
5764 if (cmd_parseIntArg("i{0,3}", argstr
, &b
, &global
, NULL
) != 0) return;
5767 b
= (global
? globalBW
: term
->blackandwhite
);
5768 tcmdlinemsgf("Monochrome: %d", b
);
5771 if (b
== 3) tcmdlinemsg("Monochrome-global can't be '3'!");
5774 term
->blackandwhite
= b
;
5782 static void cmdCursorBlink (const char *cmdname
, char *argstr
) {
5783 int b
= -1, global
= 0;
5785 if (term
== NULL
) return;
5786 if (cmd_parseIntArg("i{0,10000}", argstr
, &b
, &global
, NULL
) != 0) return;
5789 b
= (global
? opt_cursorBlink
: term
->curblink
);
5790 tcmdlinemsgf("CursorBlink: %d", b
);
5793 opt_cursorBlink
= b
;
5796 term
->curbhidden
= 0;
5804 static void cmdCursorBlinkInactive (const char *cmdname
, char *argstr
) {
5805 int b
= -1, global
= 0, toggle
= 0, *iptr
;
5807 if (term
== NULL
) return;
5808 if (cmd_parseIntArg("b", argstr
, &b
, &global
, &toggle
) != 0) return;
5810 iptr
= (global
? &opt_cursorBlinkInactive
: &term
->curblinkinactive
);
5812 if (toggle
) *iptr
= !(*iptr
); else *iptr
= b
;
5818 static void cmdIgnoreClose (const char *cmdname
, char *argstr
) {
5821 if (term
== NULL
) return;
5822 if (cmd_parseIntArg("i{-1,1}", argstr
, &b
, NULL
, NULL
) != 0) return;
5825 tcmdlinemsgf("IgnoreClose: %d", opt_ignoreclose
);
5827 opt_ignoreclose
= b
;
5832 static void cmdMaxHistory (const char *cmdname
, char *argstr
) {
5833 int b
= -1, global
= 0;
5835 if (term
== NULL
) return;
5836 if (cmd_parseIntArg("i{0,65535}", argstr
, &b
, &global
, NULL
) != 0) return;
5839 tcmdlinemsgf("MaxHistory: %d", (global
?opt_maxhistory
:term
->maxhistory
));
5841 if (!global
) tadjustmaxhistory(b
); else opt_maxhistory
= b
;
5846 static void cmdMaxDrawTimeout (const char *cmdname
, char *argstr
) {
5849 if (term
== NULL
) return;
5850 if (cmd_parseIntArg("i{100,60000}", argstr
, &b
, NULL
, NULL
) != 0) return;
5853 tcmdlinemsgf("MaxDrawTimeout: %d", opt_maxdrawtimeout
);
5855 opt_maxdrawtimeout
= b
;
5860 static void cmdTabPosition (const char *cmdname
, char *argstr
) {
5866 if (iniParseArguments(argstr
, "R-", &argstr
) != NULL
) break;
5867 if (!argstr
[0]) break;
5869 if (iniParseArguments(argstr
, "s!-R-", &s
, &argstr
) != NULL
) break;
5870 if (tolower(s
[0]) == 't') newpos
= 1;
5871 else if (tolower(s
[0]) == 'b') newpos
= 0;
5872 else if (iniParseArguments(s
, "i{0,1}", &newpos
) != NULL
) return;
5876 tcmdlinemsgf("TabPostion: %s", (opt_tabposition
== 0 ? "bottom" : "top"));
5877 } else if (opt_tabposition
!= newpos
) {
5878 opt_tabposition
= newpos
;
5887 static void cmdTabCount (const char *cmdname
, char *argstr
) {
5890 if (term
== NULL
) return;
5891 if (cmd_parseIntArg("i{1,128}", argstr
, &b
, NULL
, NULL
) != 0) return;
5894 tcmdlinemsgf("TabCount: %d", opt_tabcount
);
5895 } else if (opt_tabcount
!= b
) {
5903 static void cmdDoFullRedraw (const char *cmdname
, char *argstr
) {
5911 static void cmdTitle (const char *cmdname
, char *argstr
) {
5915 if (iniParseArguments(argstr
, "s-", &s
) != NULL
|| s
== NULL
) return;
5916 memset(term
->title
, 0, sizeof(term
->title
));
5917 while (strlen(s
) > ESC_TITLE_SIZ
) utf8choplast(s
);
5918 fprintf(stderr
, "[%s]\n", s
);
5919 strncpy(term
->title
, s
, ESC_TITLE_SIZ
);
5920 fixWindowTitle(term
);
5927 static void cmdFastRedraw (const char *cmdname
, char *argstr
) {
5928 int b
= -1, global
= 0, toggle
= 0, *iptr
;
5930 if (term
== NULL
) return;
5931 if (cmd_parseIntArg("b", argstr
, &b
, &global
, &toggle
) != 0) return;
5933 iptr
= (global
? &opt_fastredraw
: &term
->fastredraw
);
5935 if (toggle
) *iptr
= !(*iptr
); else *iptr
= b
;
5938 tcmdlinemsgf("FastRedraw: %s", (*iptr
? "yes" : "no"));
5943 static const Command commandList
[] = {
5944 {"PastePrimary", cmdPastePrimary
},
5945 {"PasteSecondary", cmdPasteSecondary
},
5946 {"PasteClipboard", cmdPasteClipboard
},
5948 {"NewTab", cmdNewTab
}, // 'noswitch' 'next' 'prev' 'first' 'last'
5949 {"CloseTab", cmdCloseTab
},
5950 {"KillTab", cmdKillTab
},
5951 {"SwitchToTab", cmdSwitchToTab
}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5952 {"MoveTabTo", cmdMoveTabTo
}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
5953 {"DefaultFG", cmdDefaultFG
},
5954 {"DefaultBG", cmdDefaultBG
},
5955 {"ScrollHistoryLineUp", cmdScrollHistoryLineUp
},
5956 {"ScrollHistoryPageUp", cmdScrollHistoryPageUp
},
5957 {"ScrollHistoryLineDown", cmdScrollHistoryLineDown
},
5958 {"ScrollHistoryPageDown", cmdScrollHistoryPageDown
},
5959 {"ScrollHistoryTop", cmdScrollHistoryTop
},
5960 {"ScrollHistoryBottom", cmdScrollHistoryBottom
},
5961 {"UTF8Locale", cmdUTF8Locale
}, // 'on', 'off'
5962 {"AudibleBell", cmdAudibleBell
},
5963 {"UrgentBell", cmdUrgentBell
},
5964 {"CommandMode", cmdCommandMode
},
5965 {"Cursor", cmdCursor
},
5966 {"ResetAttrs", cmdResetAttrs
},
5967 {"ResetCharset", cmdResetCharset
},
5968 {"Screen", cmdScreen
},
5969 {"MouseReports", cmdMouseReports
},
5970 {"Monochrome", cmdMonochrome
},
5971 {"Mono", cmdMonochrome
},
5972 {"CursorBlink", cmdCursorBlink
},
5973 {"CursorBlinkInactive", cmdCursorBlinkInactive
},
5974 {"IgnoreClose", cmdIgnoreClose
},
5975 {"MaxHistory", cmdMaxHistory
},
5976 {"MaxDrawTimeout", cmdMaxDrawTimeout
},
5977 {"TabPosition", cmdTabPosition
},
5978 {"TabCount", cmdTabCount
},
5979 {"DoFullRedraw", cmdDoFullRedraw
},
5980 {"Title", cmdTitle
},
5981 {"FastRedraw", cmdFastRedraw
},
5988 {"term", cmdTermName},
5989 {"title", cmdWinTitle},
5990 {"tabsize", cmdTabSize},
5991 {"defaultcursorfg", cmdDefaultCursorFG},
5992 {"defaultcursorbg", cmdDefaultCursorBG},
5993 {"defaultinactivecursorfg", cmdDefaultInactiveCursorFG},
5994 {"defaultinactivecursorbg", cmdDefaultInactiveCursorBG},
5995 {"defaultboldfg", cmdDefaultBoldFG},
5996 {"defaultunderlinefg", cmdDefaultUnderlineFG},
6002 static const char *findCommandCompletion (const char *str
, int slen
, const char *prev
) {
6003 const char *res
= NULL
;
6006 if (slen
< 1) return NULL
;
6007 for (int f
= 0; commandList
[f
].name
!= NULL
; ++f
) {
6008 if (strlen(commandList
[f
].name
) >= slen
&& strncasecmp(commandList
[f
].name
, str
, slen
) == 0) {
6009 if (prev
== NULL
|| phit
) return commandList
[f
].name
;
6010 if (strcasecmp(commandList
[f
].name
, prev
) == 0) phit
= 1;
6011 if (res
== NULL
) res
= commandList
[f
].name
;
6018 // !0: NewTab command
6019 static int executeCommand (const char *str
, int slen
) {
6024 if (str
== NULL
) return 0;
6025 if (slen
< 0) slen
= strlen(str
);
6027 for (int f
= 0; f
< slen
; ++f
) if (!str
[f
]) { slen
= f
; break; }
6029 while (slen
> 0 && isspace(*str
)) { ++str
; --slen
; }
6030 if (slen
< 1 || !str
[0]) return 0;
6032 for (e
= str
; slen
> 0 && !isspace(*e
); ++e
, --slen
) ;
6034 if (e
-str
> 127) return 0;
6035 cmdname
= alloca(e
-str
+8);
6036 if (cmdname
== NULL
) return 0;
6037 memcpy(cmdname
, str
, e
-str
);
6039 if (opt_disabletabs
&& strcasecmp(cmdname
, "NewTab") == 0) return 1;
6041 while (slen
> 0 && isspace(*e
)) { ++e
; --slen
; }
6042 //FIXME: ugly copypaste!
6044 for (int f
= 0; commandList
[f
].name
!= NULL
; ++f
) {
6045 if (strcasecmp(commandList
[f
].name
, cmdname
) == 0 && commandList
[f
].fn
!= NULL
) {
6046 char *left
= calloc(slen
+2, 1);
6049 if (slen
> 0) memcpy(left
, e
, slen
);
6050 //fprintf(stderr, "command: [%s]; args: [%s]\n", cmdname, left);
6051 commandList
[f
].fn(cmdname
, left
);
6060 char *left
= calloc(slen
+2, 1);
6063 if (slen
> 0) memcpy(left
, e
, slen
);
6064 processMiscCmds(cmdname
, left
);
6074 static const char *cmdpSkipStr (const char *str) {
6075 while (*str && isspace(*str)) ++str;
6076 if (str[0] && str[0] != ';') {
6080 if (*str == ';' && qch == ' ') break;
6081 if (qch != ' ' && *str == qch) { qch = ' '; ++str; continue; }
6082 if (*str == '"' || *str == '\'') {
6083 if (qch == ' ') qch = *str;
6087 if (*str++ == '\\' && *str) ++str;
6095 static void executeCommands (const char *str
) {
6097 oldTermIdx
= termidx
;
6100 if (str
== NULL
) return;
6105 while (*str
&& isspace(*str
)) ++str
;
6107 if (*str
== ';') { ++str
; continue; }
6112 if (*ce
== ';' && qch
== ' ') break;
6113 if (qch
!= ' ' && *ce
== qch
) { qch
= ' '; ++ce
; continue; }
6114 if (*ce
== '"' || *ce
== '\'') {
6115 if (qch
== ' ') qch
= *ce
;
6119 if (*ce
++ == '\\' && *ce
) ++ce
;
6122 if (executeCommand(str
, ce
-str
)) break;
6123 if (*ce
) str
= ce
+1; else break;
6126 switchToTerm(oldTermIdx
, 1);
6130 ////////////////////////////////////////////////////////////////////////////////
6131 int main (int argc
, char *argv
[]) {
6132 char *configfile
= NULL
;
6133 char *runcmd
= NULL
;
6137 for (int f
= 1; f
< argc
; f
++) {
6138 if (strcmp(argv
[f
], "-name") == 0) { ++f
; continue; }
6139 if (strcmp(argv
[f
], "-into") == 0) { ++f
; continue; }
6140 if (strcmp(argv
[f
], "-embed") == 0) { ++f
; continue; }
6141 switch (argv
[f
][0] != '-' || argv
[f
][2] ? -1 : argv
[f
][1]) {
6142 case 'e': f
= argc
+1; break;
6153 opt_term
= strdup(argv
[f
]);
6154 opt_term_locked
= 1;
6159 if (configfile
!= NULL
) free(configfile
);
6160 configfile
= strdup(argv
[f
]);
6164 if (++f
< argc
) cliLocale
= argv
[f
];
6166 case 'S': // single-tab mode
6167 opt_disabletabs
= 1;
6172 fprintf(stderr
, "%s", USAGE
);
6177 initDefaultOptions();
6178 if (configfile
== NULL
) {
6179 const char *home
= getenv("HOME");
6182 configfile
= SPrintf("%s/.sterm.rc", home
);
6183 if (loadConfig(configfile
) == 0) goto cfgdone
;
6184 free(configfile
); configfile
= NULL
;
6186 configfile
= SPrintf("%s/.config/sterm.rc", home
);
6187 if (loadConfig(configfile
) == 0) goto cfgdone
;
6188 free(configfile
); configfile
= NULL
;
6191 configfile
= SPrintf("/etc/sterm.rc");
6192 if (loadConfig(configfile
) == 0) goto cfgdone
;
6193 free(configfile
); configfile
= NULL
;
6195 configfile
= SPrintf("/etc/sterm/sterm.rc");
6196 if (loadConfig(configfile
) == 0) goto cfgdone
;
6197 free(configfile
); configfile
= NULL
;
6199 configfile
= SPrintf("./.sterm.rc");
6200 if (loadConfig(configfile
) == 0) goto cfgdone
;
6201 free(configfile
); configfile
= NULL
;
6204 if (loadConfig(configfile
) < 0) die("config file '%s' not found!", configfile
);
6207 if (configfile
!= NULL
) free(configfile
); configfile
= NULL
;
6209 for (int f
= 1; f
< argc
; f
++) {
6210 if (strcmp(argv
[f
], "-name") == 0) { ++f
; continue; }
6211 if (strcmp(argv
[f
], "-into") == 0 || strcmp(argv
[f
], "-embed") == 0) {
6212 if (opt_embed
) free(opt_embed
);
6213 opt_embed
= strdup(argv
[f
]);
6216 switch (argv
[f
][0] != '-' || argv
[f
][2] ? -1 : argv
[f
][1]) {
6220 opt_title
= strdup(argv
[f
]);
6226 opt_class
= strdup(argv
[f
]);
6231 if (opt_embed
) free(opt_embed
);
6232 opt_embed
= strdup(argv
[f
]);
6236 if (++f
< argc
) runcmd
= argv
[f
];
6239 /* eat every remaining arguments */
6240 if (++f
< argc
) opt_cmd
= &argv
[f
];
6252 fprintf(stderr
, "%s", USAGE
);
6257 setenv("TERM", opt_term
, 1);
6259 setlocale(LC_ALL
, "");
6264 if (term
->execcmd
!= NULL
) { free(term
->execcmd
); term
->execcmd
= NULL
; }
6265 tinitialize(80, 25);
6266 if (ttynew(term
) != 0) die("can't run process");
6270 if (runcmd
!= NULL
) executeCommands(runcmd
);