die if XOpenIM() fails
[k8sterm.git] / src / sterm.c
blob96a4ee44b0a092efdb701e0aeab85db8410f47bf
1 /* See LICENSE for licence details. */
2 #define VERSION "0.3.0.beta8"
4 #ifdef _XOPEN_SOURCE
5 # undef _XOPEN_SOURCE
6 #endif
7 #define _XOPEN_SOURCE 600
9 #include <alloca.h>
10 #include <ctype.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <iconv.h>
14 #include <limits.h>
15 #include <locale.h>
16 #include <pty.h>
17 #include <stdarg.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <strings.h>
22 #include <signal.h>
23 #include <sys/ioctl.h>
24 #include <sys/select.h>
25 #include <sys/stat.h>
26 #include <sys/time.h>
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #include <time.h>
30 #include <unistd.h>
31 #include <X11/Xatom.h>
32 #include <X11/Xlib.h>
33 #include <X11/Xutil.h>
34 #include <X11/cursorfont.h>
35 #include <X11/keysym.h>
37 //#include "dbglog.h"
40 // uncomment the following to use XCreateGlyphCursor() instead of XCreatePixmapCursor()
41 //#define BLANKPTR_USE_GLYPH_CURSOR
44 //#define DUMP_KEYSYMS
46 //#define KEYPAD_DUMP
48 //#define DUMP_PROG_OUTPUT
49 //#define DUMP_PROG_INPUT
51 //#define DUMP_IO_READ
52 //#define DUMP_IO_WRITE
54 #if defined(DUMP_IO_READ) || defined(DUMP_IO_WRITE)
55 # define DUMP_IO
56 #endif
58 #ifdef KEYPAD_DUMP
59 # define DUMP_KEYPAD_SWITCH(strflag,strstate) do { fprintf(stderr, "KEYPAD %s (%s)\n", (strstate), (strflag)); } while (0)
60 #else
61 # define DUMP_KEYPAD_SWITCH(strflag,strstate) ((void)(sizeof(strflag)+sizeof(strstate)))
62 #endif
65 ////////////////////////////////////////////////////////////////////////////////
66 #define USAGE \
67 "sterm " VERSION " (c) 2010-2012 st engineers and Ketmar // Vampire Avalon\n" \
68 "usage: sterm [-t title] [-c class] [-w windowid] [-T termname] [-C config_file] [-l langiconv] [-S] [-v] [-e command...]\n"
71 ////////////////////////////////////////////////////////////////////////////////
72 #define FONT "-*-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"
73 #define FONTBOLD "-*-fixed-bold-r-normal-*-18-*-*-*-*-*-*-*"
74 #define FONTTAB "-*-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*"
77 /* Default shell to use if SHELL is not set in the env */
78 #define SHELL "/bin/sh"
81 /* Terminal colors (16 first used in escape sequence) */
82 static const char *defcolornames[] = {
83 #if 1
84 /* 8 normal colors */
85 "black",
86 "red3",
87 "green3",
88 "yellow3",
89 "blue2",
90 "magenta3",
91 "cyan3",
92 "gray90",
93 /* 8 bright colors */
94 "gray50",
95 "red",
96 "green",
97 "yellow",
98 "#5c5cff",
99 "magenta",
100 "cyan",
101 "white",
102 #else
103 /* 8 normal colors */
104 "#000000",
105 "#b21818",
106 "#18b218",
107 "#b26818",
108 "#1818b2",
109 "#b218b2",
110 "#18b2b2",
111 "#b2b2b2",
112 /* 8 bright colors */
113 "#686868",
114 "#ff5454",
115 "#54ff54",
116 "#ffff54",
117 "#5454ff",
118 "#ff54ff",
119 "#54ffff",
120 "#ffffff",
121 #endif
125 /* more colors can be added after 255 to use with DefaultXX */
126 static const char *defextcolornames[] = {
127 "#cccccc", /* 256 */
128 "#333333", /* 257 */
129 /* root terminal fg and bg */
130 "#809a70", /* 258 */
131 "#002000", /* 259 */
132 /* bold and underline */
133 "#00afaf", /* 260 */
134 "#00af00", /* 261 */
138 /* Default colors (colorname index) foreground, background, cursor, unfocused cursor */
139 #define DEFAULT_FG (7)
140 #define DEFAULT_BG (0)
141 #define DEFAULT_CS (256)
142 #define DEFAULT_UCS (257)
144 #define TNAME "xterm"
146 /* double-click timeout (in milliseconds) between clicks for selection */
147 #define DOUBLECLICK_TIMEOUT (300)
148 #define TRIPLECLICK_TIMEOUT (2*DOUBLECLICK_TIMEOUT)
149 //#define SELECT_TIMEOUT 20 /* 20 ms */
150 #define DRAW_TIMEOUT 18 /* 18 ms */
152 #define TAB (8)
155 ////////////////////////////////////////////////////////////////////////////////
156 #define MAX_COLOR (511)
158 /* XEMBED messages */
159 #define XEMBED_FOCUS_IN (4)
160 #define XEMBED_FOCUS_OUT (5)
163 /* Arbitrary sizes */
164 #define ESC_TITLE_SIZ (256)
165 #define ESC_BUF_SIZ (256)
166 #define ESC_ARG_SIZ (16)
167 #define DRAW_BUF_SIZ (2048)
168 #define UTF_SIZ (4)
169 #define OBUFSIZ (256)
170 #define WBUFSIZ (256)
173 /* masks for key translation */
174 #define XK_NO_MOD (UINT_MAX)
175 #define XK_ANY_MOD (0)
178 /* misc utility macros */
179 //#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000+(t1.tv_usec-t2.tv_usec)/1000)
180 #define SERRNO strerror(errno)
181 #define MIN(a, b) ((a) < (b) ? (a) : (b))
182 #define MAX(a, b) ((a) < (b) ? (b) : (a))
183 #define LEN(a) (sizeof(a)/sizeof(a[0]))
184 #define DEFAULT(a, b) (a) = ((a) ? (a) : (b))
185 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
186 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
187 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
188 #define IS_SET(flag) (term->mode&(flag))
189 #define X2COL(x) ((x)/xw.cw)
190 #define Y2ROW(y) ((y-(opt_tabposition==1 ? xw.tabheight : 0))/xw.ch-(term!=NULL ? term->topline : 0))
191 #define IS_GFX(mode) (((mode)&(ATTR_GFX|ATTR_G1)) == ATTR_GFX || ((mode)&(ATTR_GFX1|ATTR_G1)) == (ATTR_GFX1|ATTR_G1))
194 ////////////////////////////////////////////////////////////////////////////////
195 enum glyph_attribute {
196 ATTR_NULL = 0x00,
197 ATTR_REVERSE = 0x01,
198 ATTR_UNDERLINE = 0x02,
199 ATTR_BOLD = 0x04,
200 ATTR_GFX = 0x08,
201 ATTR_DEFFG = 0x10,
202 ATTR_DEFBG = 0x20,
203 ATTR_G1 = 0x40,
204 ATTR_GFX1 = 0x80,
207 enum cursor_movement {
208 CURSOR_UP,
209 CURSOR_DOWN,
210 CURSOR_LEFT,
211 CURSOR_RIGHT,
212 CURSOR_SAVE,
213 CURSOR_LOAD
216 enum cursor_state {
217 CURSOR_DEFAULT = 0,
218 CURSOR_HIDE = 1,
219 CURSOR_WRAPNEXT = 2
222 enum glyph_state {
223 GLYPH_SET = 0x01, /* for selection only */
224 GLYPH_DIRTY = 0x02,
228 enum term_mode {
229 MODE_WRAP = 0x01,
230 MODE_INSERT = 0x02,
231 MODE_APPKEYPAD = 0x04,
232 MODE_ALTSCREEN = 0x08,
233 MODE_CRLF = 0x10,
234 MODE_MOUSEBTN = 0x20,
235 MODE_MOUSEMOTION = 0x40,
236 MODE_MOUSE = 0x20|0x40,
237 MODE_REVERSE = 0x80,
238 MODE_BRACPASTE = 0x100,
239 MODE_FOCUSEVT = 0x200,
240 MODE_DISPCTRL = 0x400, //TODO: not implemented yet
243 enum escape_state {
244 ESC_START = 0x01,
245 ESC_CSI = 0x02,
246 ESC_OSC = 0x04,
247 ESC_TITLE = 0x08,
248 ESC_ALTCHARSET = 0x10,
249 ESC_HASH = 0x20,
250 ESC_PERCENT = 0x40,
251 ESC_ALTG1 = 0x80,
254 enum window_state {
255 WIN_VISIBLE = 0x01,
256 WIN_REDRAW = 0x02,
257 WIN_FOCUSED = 0x04,
260 /* bit macro */
261 #undef B0
262 enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 };
265 ////////////////////////////////////////////////////////////////////////////////
266 typedef unsigned char uchar;
267 typedef unsigned int uint;
268 typedef unsigned long ulong;
269 typedef unsigned short ushort;
272 typedef struct __attribute__((packed)) {
273 char c[UTF_SIZ]; /* character code */
274 uchar mode; /* attribute flags */
275 ushort fg; /* foreground */
276 ushort bg; /* background */
277 uchar state; /* state flags */
278 } Glyph;
281 typedef Glyph *Line;
283 typedef struct {
284 Glyph attr; /* current char attributes */
285 int x;
286 int y;
287 char state;
288 } TCursor;
291 /* CSI Escape sequence structs */
292 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
293 typedef struct {
294 char buf[ESC_BUF_SIZ]; /* raw string */
295 int len; /* raw string length */
296 char priv;
297 int arg[ESC_ARG_SIZ];
298 int narg; /* nb of args */
299 char mode;
300 } CSIEscape;
303 /* Purely graphic info */
304 typedef struct {
305 Display *dpy;
306 Colormap cmap;
307 Window win;
308 Cursor cursor;
309 Cursor blankPtr;
310 Atom xembed;
311 XIM xim;
312 XIC xic;
313 int scr;
314 int w; /* window width */
315 int h; /* window height */
316 int bufw; /* pixmap width */
317 int bufh; /* pixmap height */
318 int ch; /* char height */
319 int cw; /* char width */
320 char state; /* focus, redraw, visible */
322 int tch; /* tab text char height */
323 Pixmap pictab;
324 int tabheight;
325 //struct timeval lastdraw;
326 } XWindow;
329 /* TODO: use better name for vars... */
330 typedef struct {
331 int mode;
332 int bx, by;
333 int ex, ey;
334 struct { int x, y; } b, e;
335 char *clip;
336 Atom xtarget;
337 int tclick1;
338 int tclick2;
339 } Selection;
342 /* Drawing Context */
343 typedef struct {
344 ulong *col; //[LEN(colorname) < 256 ? 256 : LEN(colorname)];
345 GC gc;
346 struct {
347 int ascent;
348 int descent;
349 short lbearing;
350 short rbearing;
351 XFontSet set;
352 Font fid;
353 } font[3];
354 } DC;
357 #define CMDLINE_SIZE (256)
359 /* Internal representation of the screen */
360 typedef struct {
361 int cmdfd;
362 int dead;
363 int exitcode;
364 int needConv; /* 0: utf-8 locale */
365 int audiblebell;
367 int row; /* nb row */
368 int col; /* nb col */
369 int topline; /* top line for drawing (0: no history; 1: show one history line; etc) */
370 int linecount; /* full, with history */
371 int maxhistory;/* max history lines; 0: none; <0: infinite */
372 Line *line; /* screen */
373 Line *alt; /* alternate screen */
374 char *dirty; /* dirtyness of lines */
375 TCursor c; /* cursor */
376 int top; /* top scroll limit */
377 int bot; /* bottom scroll limit */
378 int mode; /* terminal mode flags */
379 int esc; /* escape state flags */
381 TCursor csaved; /* saved cursor info */
382 // old cursor position
383 int oldcx;
384 int oldcy;
386 char title[ESC_TITLE_SIZ+1];
387 int titlelen;
389 int mouseob;
390 int mouseox;
391 int mouseoy;
393 char obuf[OBUFSIZ];
394 #ifdef DUMP_PROG_OUTPUT
395 int xobuflen;
396 #endif
397 int obuflen;
399 char ubuf[UTF_SIZ];
400 int ubufpos;
402 char drawbuf[DRAW_BUF_SIZ];
404 char wrbuf[WBUFSIZ];
405 int wrbufsize;
406 int wrbufused;
407 int wrbufpos;
409 CSIEscape escseq;
410 Selection sel;
411 pid_t pid;
412 int lastDrawTime;
414 char *execcmd;
416 Pixmap picbuf;
417 int picbufw;
418 int picbufh;
420 ushort deffg;
421 ushort defbg;
423 int wantRedraw;
425 int lastActiveTime;
427 int cmdMode;
428 char cmdline[UTF_SIZ*CMDLINE_SIZE];
429 int cmdofs;
430 char cmdc[UTF_SIZ+1];
431 int cmdcl;
432 } Term;
435 ////////////////////////////////////////////////////////////////////////////////
436 /* Globals */
437 static ushort *unimap = NULL; // 0 or 65535 items; 127: special; high bit set: use alt(gfx) mode; 0: empty
439 static char **opt_cmd = NULL;
440 static char *opt_title = NULL;
441 static char *opt_embed = NULL;
442 static char *opt_class = NULL;
443 static char *opt_term = NULL;
444 static char *opt_fontnorm = NULL;
445 static char *opt_fontbold = NULL;
446 static char *opt_fonttab = NULL;
447 static char *opt_shell = NULL;
448 static char *opt_colornames[512];
449 static int opt_term_locked = 0;
450 static int opt_doubleclick_timeout = DOUBLECLICK_TIMEOUT;
451 static int opt_tripleclick_timeout = TRIPLECLICK_TIMEOUT;
452 static int opt_tabsize = TAB;
453 static int defaultFG = DEFAULT_FG;
454 static int defaultBG = DEFAULT_BG;
455 static int defaultCursorFG = 0;
456 static int defaultCursorBG = DEFAULT_CS;
457 static int defaultCursorInactiveFG = 0;
458 static int defaultCursorInactiveBG = DEFAULT_UCS;
459 static int defaultBoldFG = -1;
460 static int defaultUnderlineFG = -1;
461 static int normalTabFG = 258;
462 static int normalTabBG = 257;
463 static int activeTabFG = 258;
464 static int activeTabBG = 0;
465 static int opt_maxhistory = 512;
466 static int opt_ptrblank = 2000; // delay; 0: never
467 static int opt_tabcount = 6;
468 static int opt_tabposition = 0;
469 static int opt_drawtimeout = DRAW_TIMEOUT;
470 static int opt_disabletabs = 0;
471 static int opt_audiblebell = 1;
472 static int ptrBlanked = 0;
473 static int ptrLastMove = 0;
475 static Term **term_array = NULL;
476 static int term_count = 0;
477 static int term_array_size = 0;
478 static Term *term; // current terminal
479 static int termidx; // current terminal index; DON'T RELAY ON IT!
480 static int updateTabBar;
481 static int lastDrawTime = 0;
482 static char *lastSelStr = NULL;
483 //static int lastSelLength = 0;
485 static int exitcode = 0;
487 static DC dc;
488 static XWindow xw;
490 static Atom XA_VT_SELECTION;
491 static Atom XA_CLIPBOARD;
492 static Atom XA_UTF8;
493 static Atom XA_TARGETS;
494 static Atom XA_NETWM_NAME;
497 ////////////////////////////////////////////////////////////////////////////////
498 typedef struct {
499 KeySym src;
500 KeySym dst;
501 } KeyTransDef;
504 static KeyTransDef *keytrans = NULL;
505 static int keytrans_size = 0;
506 static int keytrans_used = 0;
509 typedef struct {
510 KeySym key;
511 uint mask;
512 int kp;
513 char *str;
514 } KeyInfoDef;
517 static KeyInfoDef *keybinds = NULL;
518 static int keybinds_size = 0;
519 static int keybinds_used = 0;
521 static KeyInfoDef *keymap = NULL;
522 static int keymap_size = 0;
523 static int keymap_used = 0;
526 ////////////////////////////////////////////////////////////////////////////////
527 static void executeCommands (const char *str);
529 static void ttyresize (void);
530 static void tputc (const char *c); // `c` is utf-8
531 static void ttywrite (const char *s, size_t n);
532 static void tsetdirt (int top, int bot);
533 static void tfulldirt (void);
535 static void xseturgency (int add);
536 static void xfixsel (void);
537 static void xblankPointer (void);
538 static void xunblankPointer (void);
540 static void draw (int forced);
542 static void tcmdput (const char *s, int len);
545 static inline void ttywritestr (const char *s) { if (s != NULL && s[0]) ttywrite(s, strlen(s)); }
548 ////////////////////////////////////////////////////////////////////////////////
550 static void trimstr (char *s) {
551 char *e;
553 while (*s && isspace(*s)) ++s;
554 for (e = s+strlen(s); e > s; --e) if (!isspace(e[-1])) break;
555 if (e <= s) *s = 0; else *e = 0;
559 // parse the argument list
560 // return error message or NULL
561 // format:
562 // '*': skip
563 // 's': string (char *)
564 // 'i': integer (int *)
565 // 'b': boolean (int *)
566 // '|': optional arguments follows
567 // '.': stop parsing, ignore rest
568 // 'R': stop parsing, set rest ptr (char *)
569 // string modifiers (also for 'R'):
570 // '!' -- don't allow empty strings
571 // '-' -- trim spaces
572 // int modifiers:
573 // {lo,hi}
574 // {,hi}
575 // {lo}
576 // WARNING! `line` will be modified!
577 // WARNING! string pointers will point INSIDE `line`, so don't discard it!
578 // UGLY! REWRITE!
579 const char *iniParseArguments (char *line, const char *fmt, ...) {
580 va_list ap;
581 int inOptional = 0;
583 if (line == NULL) return "alas";
584 trimstr(line);
585 va_start(ap, fmt);
586 while (*fmt) {
587 char spec = *fmt++, *args;
589 if (spec == '|') { inOptional = 1; continue; }
590 if (spec == '.') { va_end(ap); return NULL; }
592 while (*line && isspace(*line)) ++line;
593 if (*line == '#') *line = 0;
595 if (spec == 'R') {
596 char **p = va_arg(ap, char **);
597 int noempty = 0;
599 while (*fmt) {
600 if (*fmt == '!') { ++fmt; noempty = 1; }
601 else if (*fmt == '-') { ++fmt; trimstr(line); }
602 else break;
604 va_end(ap);
605 if (noempty && !line[0]) return "invalid empty arg";
606 if (p != NULL) *p = line;
607 return NULL;
610 if (!line[0]) {
611 // end of line, stop right here
612 va_end(ap);
613 if (!inOptional) return "out of args";
614 return NULL;
617 args = line;
619 char *dest = args, qch = '#';
620 int n;
622 if (line[0] == '"' || line[0] == '\'') qch = *line++;
624 while (*line && *line != qch) {
625 if (qch == '#' && isspace(*line)) break;
627 if (*line == '\\') {
628 switch (*(++line)) {
629 case 'n': *dest++ = '\n'; ++line; break;
630 case 'r': *dest++ = '\r'; ++line; break;
631 case 't': *dest++ = '\t'; ++line; break;
632 case 'a': *dest++ = '\a'; ++line; break;
633 case 'e': *dest++ = '\x1b'; ++line; break; // esc
634 case 's': *dest++ = ' '; ++line; break;
635 case 'x': // hex
636 ++line;
637 if (!isxdigit(*line)) { va_end(ap); return "invalid hex escape"; }
638 n = toupper(*line)-'0'; if (n > 9) n -= 7;
639 ++line;
640 if (isxdigit(*line)) {
641 int b = toupper(*line)-'0'; if (b > 9) b -= 7;
643 n = (n*16)+b;
644 ++line;
646 *dest++ = n;
647 break;
648 case '0': // octal
649 n = 0;
650 for (int f = 0; f < 4; ++f) {
651 if (*line < '0' || *line > '7') break;
652 n = (n*8)+(line[0]-'0');
653 if (n > 255) { va_end(ap); return "invalid oct escape"; }
654 ++line;
656 if (n == 0) { va_end(ap); return "invalid oct escape"; }
657 *dest++ = n;
658 break;
659 case '1'...'9': // decimal
660 n = 0;
661 for (int f = 0; f < 3; ++f) {
662 if (*line < '0' || *line > '9') break;
663 n = (n*8)+(line[0]-'0');
664 if (n > 255) { va_end(ap); return "invalid dec escape"; }
665 ++line;
667 if (n == 0) { va_end(ap); return "invalid oct escape"; }
668 *dest++ = n;
669 break;
670 default:
671 *dest++ = *line++;
672 break;
674 } else {
675 *dest++ = *line++;
678 if (qch != '#') {
679 if (*line != qch) return "unfinished string";
680 ++line;
681 } else if (*line != '#') ++line;
682 *dest = 0;
684 // now process and convert argument
685 switch (spec) {
686 case '*': /* skip */
687 break;
688 case 's': { /* string */
689 int noempty = 0, trim = 0;
690 char **p;
692 for (;;) {
693 if (*fmt == '!') { noempty = 1; ++fmt; }
694 else if (*fmt == '-') { trim = 1; ++fmt; }
695 else break;
698 if (trim) trimstr(args);
700 if (noempty && !args[0]) { va_end(ap); return "invalid empty string"; }
701 p = va_arg(ap, char **);
702 if (p != NULL) *p = args;
703 } break;
704 case 'i': /* int */
705 if (!args[0]) {
706 va_end(ap);
707 return "invalid integer";
708 } else {
709 int *p = va_arg(ap, int *);
710 long int n;
711 char *eptr;
713 trimstr(args);
714 n = strtol(args, &eptr, 0);
715 if (*eptr) { va_end(ap); return "invalid integer"; }
717 if (*fmt == '{') {
718 // check min/max
719 int minmax[2], haveminmax[2];
721 haveminmax[0] = haveminmax[1] = 0;
722 minmax[0] = minmax[1] = 0;
723 ++fmt;
724 for (int f = 0; f < 2; ++f) {
725 if (isdigit(*fmt) || *fmt == '-' || *fmt == '+') {
726 int neg = 0;
727 haveminmax[f] = 1;
728 if (*fmt == '-') neg = 1;
729 if (!isdigit(*fmt)) {
730 ++fmt;
731 if (!isdigit(*fmt)) { va_end(ap); return "invalid integer bounds"; }
733 while (isdigit(*fmt)) {
734 minmax[f] = minmax[f]*10+(fmt[0]-'0');
735 ++fmt;
737 if (neg) minmax[f] = -minmax[f];
738 //fprintf(stderr, "got: %d\n", minmax[f]);
740 if (*fmt == ',') {
741 if (f == 1) { va_end(ap); return "invalid integer bounds: extra comma"; }
742 // do nothing, we are happy
743 ++fmt;
744 } else if (*fmt == '}') {
745 // ok, done
746 break;
747 } else { va_end(ap); return "invalid integer bounds"; }
749 if (*fmt != '}') { va_end(ap); return "invalid integer bounds"; }
750 ++fmt;
752 //fprintf(stderr, "b: (%d,%d) (%d,%d)\n", haveminmax[0], minmax[0], haveminmax[1], minmax[1]);
753 if ((haveminmax[0] && n < minmax[0]) || (haveminmax[1] && n > minmax[1])) { va_end(ap); return "integer out of bounds"; }
756 if (p) *p = n;
758 break;
759 case 'b': { /* bool */
760 int *p = va_arg(ap, int *);
762 trimstr(args);
763 if (!args[0]) { va_end(ap); return "invalid boolean"; }
764 if (strcasecmp(args, "true") == 0 || strcasecmp(args, "on") == 0 ||
765 strcasecmp(args, "tan") == 0 || strcasecmp(args, "1") == 0) {
766 if (p) *p = 1;
767 } else if (strcasecmp(args, "false") == 0 || strcasecmp(args, "off") == 0 ||
768 strcasecmp(args, "ona") == 0 || strcasecmp(args, "0") == 0) {
770 if (p) *p = 0;
771 } else {
772 va_end(ap);
773 return "invalid boolean";
775 } break;
776 default:
777 va_end(ap);
778 return "invalid format specifier";
781 va_end(ap);
782 while (*line && isspace(*line)) ++line;
783 if (!line[0] || line[0] == '#') return NULL;
784 return "extra args";
788 ////////////////////////////////////////////////////////////////////////////////
789 // UTF-8
790 static int utf8decode (const char *s, ulong *u) {
791 uchar c;
792 int n, rtn;
794 rtn = 1;
795 c = *s;
796 if (~c & B7) { /* 0xxxxxxx */
797 *u = c;
798 return rtn;
799 } else if ((c & (B7|B6|B5)) == (B7|B6)) { /* 110xxxxx */
800 *u = c&(B4|B3|B2|B1|B0);
801 n = 1;
802 } else if ((c & (B7|B6|B5|B4)) == (B7|B6|B5)) { /* 1110xxxx */
803 *u = c&(B3|B2|B1|B0);
804 n = 2;
805 } else if ((c & (B7|B6|B5|B4|B3)) == (B7|B6|B5|B4)) { /* 11110xxx */
806 *u = c & (B2|B1|B0);
807 n = 3;
808 } else {
809 goto invalid;
811 ++s;
812 for (int f = n; f > 0; --f, ++rtn, ++s) {
813 c = *s;
814 if ((c & (B7|B6)) != B7) goto invalid; /* 10xxxxxx */
815 *u <<= 6;
816 *u |= c & (B5|B4|B3|B2|B1|B0);
818 if ((n == 1 && *u < 0x80) ||
819 (n == 2 && *u < 0x800) ||
820 (n == 3 && *u < 0x10000) ||
821 (*u >= 0xD800 && *u <= 0xDFFF)) {
822 goto invalid;
824 return rtn;
825 invalid:
826 *u = 0xFFFD;
827 return rtn;
831 static int utf8encode (ulong u, char *s) {
832 uchar *sp;
833 ulong uc;
834 int n;
836 sp = (uchar *)s;
837 uc = u&0x1fffff;
838 if (uc < 0x80) {
839 *sp = uc; /* 0xxxxxxx */
840 return 1;
841 } else if (uc < 0x800) {
842 *sp = (uc >> 6) | (B7|B6); /* 110xxxxx */
843 n = 1;
844 } else if (uc < 0x10000) {
845 *sp = (uc >> 12) | (B7|B6|B5); /* 1110xxxx */
846 n = 2;
847 } else if (uc <= 0x10FFFF) {
848 *sp = (uc >> 18) | (B7|B6|B5|B4); /* 11110xxx */
849 n = 3;
850 } else {
851 goto invalid;
853 ++sp;
854 for (int f = n; f > 0; --f, ++sp) *sp = ((uc >> 6*(f-1)) & (B5|B4|B3|B2|B1|B0)) | B7; /* 10xxxxxx */
855 return n+1;
856 invalid:
857 /* U+FFFD */
858 *s++ = '\xEF';
859 *s++ = '\xBF';
860 *s = '\xBD';
861 return 3;
865 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
866 UTF-8 otherwise return 0 */
867 static int isfullutf8 (const char *s, int b) {
868 uchar *c1, *c2, *c3;
870 c1 = (uchar *) s;
871 c2 = (uchar *) ++s;
872 c3 = (uchar *) ++s;
873 if (b < 1) return 0;
874 if ((*c1&(B7|B6|B5)) == (B7|B6) && b == 1) return 0;
875 if ((*c1&(B7|B6|B5|B4)) == (B7|B6|B5) && (b == 1 || (b == 2 && (*c2&(B7|B6)) == B7))) return 0;
876 if ((*c1&(B7|B6|B5|B4|B3)) == (B7|B6|B5|B4) && (b == 1 || (b == 2 && (*c2&(B7|B6)) == B7) || (b == 3 && (*c2&(B7|B6)) == B7 && (*c3&(B7|B6)) == B7))) return 0;
877 return 1;
881 static int utf8size (const char *s) {
882 uchar c = *s;
884 if (~c&B7) return 1;
885 if ((c&(B7|B6|B5)) == (B7|B6)) return 2;
886 if ((c&(B7|B6|B5|B4)) == (B7|B6|B5)) return 3;
887 return 4;
891 static int utf8strlen (const char *s) {
892 int len = 0;
894 while (*s) {
895 if (((unsigned char)(s[0])&0xc0) == 0xc0 || ((unsigned char)(s[0])&0x80) == 0) ++len;
896 ++s;
898 return len;
902 static void utf8choplast (char *s) {
903 int lastpos = 0;
905 for (char *t = s; *t; ++t) {
906 if (((unsigned char)(t[0])&0xc0) == 0xc0 || ((unsigned char)(t[0])&0x80) == 0) lastpos = (int)(t-s);
908 s[lastpos] = 0;
912 ////////////////////////////////////////////////////////////////////////////////
913 // utilities
914 static char *SPrintfVA (const char *fmt, va_list vaorig) {
915 char *buf = NULL;
916 int olen, len = 128;
918 buf = malloc(len);
919 if (buf == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
920 for (;;) {
921 char *nb;
922 va_list va;
924 va_copy(va, vaorig);
925 olen = vsnprintf(buf, len, fmt, va);
926 va_end(va);
927 if (olen >= 0 && olen < len) return buf;
928 if (olen < 0) olen = len*2-1;
929 nb = realloc(buf, olen+1);
930 if (nb == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
931 buf = nb;
932 len = olen+1;
937 static __attribute__((format(printf,1,2))) char *SPrintf (const char *fmt, ...) {
938 char *buf = NULL;
939 va_list va;
941 va_start(va, fmt);
942 buf = SPrintfVA(fmt, va);
943 va_end(va);
944 return buf;
948 static __attribute__((noreturn)) __attribute__((format(printf,1,2))) void die (const char *errstr, ...) {
949 va_list ap;
951 fprintf(stderr, "FATAL: ");
952 va_start(ap, errstr);
953 vfprintf(stderr, errstr, ap);
954 va_end(ap);
955 fprintf(stderr, "\n");
956 exit(EXIT_FAILURE);
960 ////////////////////////////////////////////////////////////////////////////////
961 // getticks
962 static struct timespec mclk_sttime; // starting time of monotonic clock
965 static void mclock_init (void) {
966 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &mclk_sttime);
970 static uint mclock_ticks (void) {
971 struct timespec tp;
973 clock_gettime(CLOCK_MONOTONIC /*CLOCK_MONOTONIC_RAW*/, &tp);
974 tp.tv_sec -= mclk_sttime.tv_sec;
975 return tp.tv_sec*1000+(tp.tv_nsec/1000000);
979 ////////////////////////////////////////////////////////////////////////////////
980 // locale conversions
981 static iconv_t icFromLoc;
982 static iconv_t icToLoc;
983 static int needConversion = 0;
984 static const char *cliLocale = NULL;
987 static void initLCConversion (void) {
988 const char *lct = setlocale(LC_CTYPE, NULL);
989 char *cstr;
991 needConversion = 0;
992 if (cliLocale == NULL) {
993 if (strrchr(lct, '.') != NULL) lct = strrchr(lct, '.')+1;
994 } else {
995 lct = cliLocale;
997 if (strcasecmp(lct, "utf8") == 0 || strcasecmp(lct, "utf-8") == 0) return;
998 //fprintf(stderr, "locale: [%s]\n", lct);
999 icFromLoc = iconv_open("UTF-8", lct);
1000 if (icFromLoc == (iconv_t)-1) die("can't initialize locale conversion");
1001 cstr = SPrintf("%s//TRANSLIT", lct);
1002 icToLoc = iconv_open(cstr, "UTF-8");
1003 free(cstr);
1004 if (icToLoc == (iconv_t)-1) die("can't initialize locale conversion");
1005 needConversion = 1;
1009 static int loc2utf (char *dest, const char *src, int len) {
1010 if (needConversion) {
1011 char *ibuf, *obuf;
1012 size_t il, ol, ool;
1014 ibuf = (char *)src;
1015 obuf = dest;
1016 il = len;
1017 ool = ol = il*4;
1018 il = iconv(icFromLoc, &ibuf, &il, &obuf, &ol);
1019 if (il == (size_t)-1) return 0;
1020 return ool-ol;
1021 } else {
1022 if (len > 0) memmove(dest, src, len);
1023 return len;
1028 static int utf2loc (char *dest, const char *src, int len) {
1029 if (needConversion) {
1030 char *ibuf, *obuf;
1031 size_t il, ol, ool;
1033 ibuf = (char *)src;
1034 obuf = dest;
1035 il = len;
1036 ool = ol = il*4;
1037 il = iconv(icToLoc, &ibuf, &il, &obuf, &ol);
1038 if (il == (size_t)-1) return 0;
1039 return ool-ol;
1040 } else {
1041 if (len > 0) memmove(dest, src, len);
1042 return len;
1047 ////////////////////////////////////////////////////////////////////////////////
1048 static void fixWindowTitle (const Term *t) {
1049 const char *title = (t != NULL) ? t->title : NULL;
1051 if (title == NULL || !title[0]) {
1052 title = opt_title;
1053 if (title == NULL) title = "";
1055 XStoreName(xw.dpy, xw.win, title);
1056 XChangeProperty(xw.dpy, xw.win, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, (const unsigned char *)title, strlen(title));
1060 // find latest active terminal (but not current %-)
1061 static int findTermToSwitch (void) {
1062 int maxlat = -1, idx = -1;
1064 for (int f = 0; f < term_count; ++f) {
1065 if (term != term_array[f] && term_array[f]->lastActiveTime > maxlat) {
1066 maxlat = term_array[f]->lastActiveTime;
1067 idx = f;
1070 if (idx < 0) {
1071 if (termidx == 0) idx = 0; else idx = termidx+1;
1072 if (idx > term_count) idx = term_count-1;
1074 return idx;
1078 static void switchToTerm (int idx, int redraw) {
1079 if (idx >= 0 && idx < term_count && term_array[idx] != NULL && term_array[idx] != term) {
1080 if (term != NULL) term->lastActiveTime = mclock_ticks();
1081 termidx = idx;
1082 term = term_array[termidx];
1083 xseturgency(0);
1084 tfulldirt();
1085 fixWindowTitle(term);
1086 updateTabBar = 1;
1087 if (redraw) draw(1);
1088 //FIXME: optimize memory allocations
1089 if (term->sel.clip != NULL && term->sel.bx >= 0) {
1090 if (lastSelStr != NULL) free(lastSelStr);
1091 lastSelStr = strdup(term->sel.clip);
1092 //fprintf(stderr, "newsel: [%s]\n", lastSelStr);
1094 xfixsel();
1095 //fprintf(stderr, "term #%d\n", termidx);
1096 //fprintf(stderr, "needConv: %d\n", term->needConv);
1101 static Term *termalloc (void) {
1102 Term *t;
1104 if (term_count >= term_array_size) {
1105 int newsz = (term_count==0) ? 1 : term_array_size+64;
1106 Term **n = realloc(term_array, sizeof(Term *)*newsz);
1108 if (n == NULL && term_count == 0) die("out of memory!");
1109 term_array = n;
1110 term_array_size = newsz;
1112 if ((t = calloc(1, sizeof(Term))) == NULL) return NULL;
1113 t->wrbufsize = WBUFSIZ;
1114 t->deffg = defaultFG;
1115 t->defbg = defaultBG;
1116 t->dead = 1;
1117 t->needConv = (needConversion ? 1 : 0);
1118 t->audiblebell = opt_audiblebell;
1119 term_array[term_count++] = t;
1120 return t;
1124 // newer delete last terminal!
1125 static void termfree (int idx) {
1126 if (idx >= 0 && idx < term_count && term_array[idx] != NULL) {
1127 Term *t = term_array[idx];
1129 if (t->pid != 0) {
1130 kill(t->pid, SIGKILL);
1131 return;
1133 if (t->cmdfd >= 0) {
1134 close(t->cmdfd);
1135 t->cmdfd = -1;
1137 exitcode = t->exitcode;
1138 if (idx == termidx) {
1139 if (term_count > 1) {
1140 t->dead = 1;
1141 //switchToTerm((idx > 0) ? idx-1 : 1, 0);
1142 switchToTerm(findTermToSwitch(), 0);
1143 return;
1145 term = NULL;
1147 for (int y = 0; y < t->row; ++y) free(t->alt[y]);
1148 for (int y = 0; y < t->linecount; ++y) {
1149 //fprintf(stderr, "y=%d\n", y);
1150 free(t->line[y]);
1152 free(t->dirty);
1153 free(t->alt);
1154 free(t->line);
1155 if (t->execcmd != NULL) free(t->execcmd);
1156 // condense array
1157 if (termidx > idx) {
1158 // not current, and current at the right
1159 --termidx;
1161 for (int f = idx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
1162 --term_count;
1163 XFreePixmap(xw.dpy, t->picbuf);
1164 free(t);
1169 static void termcleanup (void) {
1170 int f = 0;
1172 while (f < term_count) {
1173 if (term_array[f]->dead) {
1174 termfree(f);
1175 } else {
1176 ++f;
1179 updateTabBar = 1;
1180 draw(1);
1184 //FIXME: is it safe to assume that signal interrupted main program?
1185 static void sigchld (int a) {
1186 //if (waitpid(term->pid, &stat, 0) < 0) die("waiting for pid %hd failed: %s", term->pid, SERRNO);
1187 for (;;) {
1188 int stat = 0;
1189 pid_t res = waitpid(-1, &stat, WNOHANG);
1191 if (res == (pid_t)-1 || res == 0) break;
1193 for (int f = 0; f < term_count; ++f) {
1194 if (term_array[f]->pid == res) {
1195 // this terminal should die
1196 //if (term_count == 1) exit(0);
1197 //close(term_array[f]->cmdfd);
1198 //term_array[f]->cmdfd = -1;
1199 term_array[f]->dead = 1;
1200 term_array[f]->pid = 0;
1201 term_array[f]->exitcode = (WIFEXITED(stat)) ? WEXITSTATUS(stat) : 127;
1202 //fprintf(stderr, "exitcode=%d\n", term_array[f]->exitcode);
1203 updateTabBar = 1;
1204 break;
1208 signal(SIGCHLD, sigchld);
1212 ////////////////////////////////////////////////////////////////////////////////
1213 static void keytrans_reset (void) {
1214 if (keytrans) free(keytrans);
1215 keytrans = NULL;
1216 keytrans_size = 0;
1217 keytrans_used = 0;
1221 static void keytrans_add (const char *src, const char *dst) {
1222 KeySym kssrc = XStringToKeysym(src);
1223 KeySym ksdst = XStringToKeysym(dst);
1225 if (kssrc == NoSymbol) die("invalid keysym: '%s'", src);
1226 if (ksdst == NoSymbol) die("invalid keysym: '%s'", dst);
1227 if (kssrc == ksdst) return; // idiot
1229 for (int f = 0; f < keytrans_used; ++f) {
1230 if (keytrans[f].src == kssrc) {
1231 // replace
1232 keytrans[f].dst = ksdst;
1233 return;
1237 if (keytrans_used >= keytrans_size) {
1238 int newsize = keytrans_size+64;
1239 KeyTransDef *n = realloc(keytrans, sizeof(KeyTransDef)*newsize);
1241 if (n == NULL) die("out of memory");
1242 keytrans_size = newsize;
1243 keytrans = n;
1245 keytrans[keytrans_used].src = kssrc;
1246 keytrans[keytrans_used].dst = ksdst;
1247 ++keytrans_used;
1251 ////////////////////////////////////////////////////////////////////////////////
1252 static void parsekeyname (const char *str, KeySym *ks, uint *mask, int *kp) {
1253 char *s = alloca(strlen(str)+1);
1255 if (s == NULL) die("out of memory");
1256 strcpy(s, str);
1257 *kp = 0;
1258 *ks = NoSymbol;
1259 *mask = XK_NO_MOD;
1261 while (*s) {
1262 char *e, oc;
1263 uint mm = 0;
1264 int mod = 1;
1266 while (*s && isspace(*s)) ++s;
1267 for (e = s; *e && !isspace(*e) && *e != '+'; ++e) ;
1268 oc = *e; *e = 0;
1270 if (strcasecmp(s, "alt") == 0) mm = Mod1Mask;
1271 else if (strcasecmp(s, "win") == 0) mm = Mod4Mask;
1272 else if (strcasecmp(s, "ctrl") == 0) mm = ControlMask;
1273 else if (strcasecmp(s, "shift") == 0) mm = ShiftMask;
1274 else if (strcasecmp(s, "any") == 0) mm = XK_NO_MOD; //!
1275 else if (strcasecmp(s, "kpad") == 0) *kp = 1;
1276 else {
1277 mod = 0;
1278 if ((*ks = XStringToKeysym(s)) == NoSymbol) break;
1279 //fprintf(stderr, "[%s]\n", s);
1282 *e = oc;
1283 s = e;
1284 while (*s && isspace(*s)) ++s;
1285 if (mod) {
1286 if (*s != '+') { *ks = NoSymbol; break; }
1287 ++s;
1288 if (mm != 0) {
1289 if (mm == XK_NO_MOD) *mask = XK_ANY_MOD;
1290 else if (*mask == XK_NO_MOD) *mask = mm;
1291 else if (*mask != XK_ANY_MOD) *mask |= mm;
1293 } else {
1294 if (*s) { *ks = NoSymbol; break; }
1297 if (*ks == NoSymbol) die("invalid key name: '%s'", str);
1298 //fprintf(stderr, "mask=0x%08x, kp=%d\n", *mask, *kp);
1302 ////////////////////////////////////////////////////////////////////////////////
1303 static void keybinds_reset (void) {
1304 if (keybinds) free(keybinds);
1305 keybinds = NULL;
1306 keybinds_size = 0;
1307 keybinds_used = 0;
1311 static void keybind_add (const char *key, const char *act) {
1312 KeySym ks;
1313 uint mask;
1314 int kp;
1316 parsekeyname(key, &ks, &mask, &kp);
1318 for (int f = 0; f < keybinds_used; ++f) {
1319 if (keybinds[f].key == ks && keybinds[f].mask == mask) {
1320 // replace or remove
1321 free(keybinds[f].str);
1322 if (act == NULL || !act[0]) {
1323 // remove
1324 for (int c = f+1; c < keybinds_used; ++c) keybinds[c-1] = keybinds[c];
1325 } else {
1326 // replace
1327 if ((keybinds[f].str = strdup(act)) == NULL) die("out of memory");
1329 return;
1333 if (keybinds_used >= keybinds_size) {
1334 int newsize = keybinds_size+64;
1335 KeyInfoDef *n = realloc(keybinds, sizeof(KeyInfoDef)*newsize);
1337 if (n == NULL) die("out of memory");
1338 keybinds_size = newsize;
1339 keybinds = n;
1341 keybinds[keybinds_used].key = ks;
1342 keybinds[keybinds_used].mask = mask;
1343 keybinds[keybinds_used].kp = 0;
1344 if ((keybinds[keybinds_used].str = strdup(act)) == NULL) die("out of memory");
1345 ++keybinds_used;
1349 ////////////////////////////////////////////////////////////////////////////////
1350 static void keymap_reset (void) {
1351 if (keymap) free(keymap);
1352 keymap = NULL;
1353 keymap_size = 0;
1354 keymap_used = 0;
1358 static void keymap_add (const char *key, const char *act) {
1359 KeySym ks;
1360 uint mask;
1361 int kp;
1363 parsekeyname(key, &ks, &mask, &kp);
1365 for (int f = 0; f < keymap_used; ++f) {
1366 if (keymap[f].key == ks && keymap[f].mask == mask && keymap[f].kp == kp) {
1367 // replace or remove
1368 free(keymap[f].str);
1369 if (act == NULL) {
1370 // remove
1371 for (int c = f+1; c < keymap_used; ++c) keymap[c-1] = keymap[c];
1372 } else {
1373 // replace
1374 if ((keymap[f].str = strdup(act)) == NULL) die("out of memory");
1376 return;
1380 if (keymap_used >= keymap_size) {
1381 int newsize = keymap_size+128;
1382 KeyInfoDef *n = realloc(keymap, sizeof(KeyInfoDef)*newsize);
1384 if (n == NULL) die("out of memory");
1385 keymap_size = newsize;
1386 keymap = n;
1388 keymap[keymap_used].key = ks;
1389 keymap[keymap_used].mask = mask;
1390 keymap[keymap_used].kp = kp;
1391 if ((keymap[keymap_used].str = strdup(act)) == NULL) die("out of memory");
1392 ++keymap_used;
1396 ////////////////////////////////////////////////////////////////////////////////
1397 // selection
1398 static void inline markDirty (int lineno, int flag) {
1399 if (term != NULL && lineno >= 0 && lineno < term->row) {
1400 term->dirty[lineno] |= flag;
1401 term->wantRedraw = 1;
1402 term->lastDrawTime = mclock_ticks(); //FIXME: avoid excess redraw?
1407 static void selinit (void) {
1408 term->sel.tclick1 = term->sel.tclick2 = mclock_ticks();
1409 term->sel.mode = 0;
1410 term->sel.bx = -1;
1411 term->sel.clip = NULL;
1412 term->sel.xtarget = XA_UTF8;
1413 //if (term->sel.xtarget == None) term->sel.xtarget = XA_STRING;
1417 static void selhide (void) {
1418 if (term->sel.bx != -1) {
1419 term->sel.mode = 0;
1420 term->sel.bx = -1;
1421 tfulldirt();
1426 static inline int selected (int x, int y) {
1427 if (term->sel.bx == -1) return 0;
1428 if (term->sel.ey == y && term->sel.by == y) {
1429 int bx = MIN(term->sel.bx, term->sel.ex);
1430 int ex = MAX(term->sel.bx, term->sel.ex);
1432 return BETWEEN(x, bx, ex);
1434 return
1435 ((term->sel.b.y < y && y < term->sel.e.y) || (y == term->sel.e.y && x <= term->sel.e.x)) ||
1436 (y == term->sel.b.y && x >= term->sel.b.x && (x <= term->sel.e.x || term->sel.b.y != term->sel.e.y));
1440 static void getbuttoninfo (XEvent *e, int *b, int *x, int *y) {
1441 if (b != NULL) *b = e->xbutton.button;
1442 if (x != NULL) *x = X2COL(e->xbutton.x);
1443 if (y != NULL) *y = Y2ROW(e->xbutton.y);
1444 term->sel.b.x = (term->sel.by < term->sel.ey ? term->sel.bx : term->sel.ex);
1445 term->sel.b.y = MIN(term->sel.by, term->sel.ey);
1446 term->sel.e.x = (term->sel.by < term->sel.ey ? term->sel.ex : term->sel.bx);
1447 term->sel.e.y = MAX(term->sel.by, term->sel.ey);
1451 static void mousereport (XEvent *e) {
1452 int x = X2COL(e->xbutton.x);
1453 int y = Y2ROW(e->xbutton.y);
1454 int button = e->xbutton.button;
1455 int state = e->xbutton.state;
1456 //char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
1457 char buf[16];
1459 if (term == NULL) return;
1460 if (x < 0 || x >= term->col || y < 0 || y >= term->row) return;
1461 sprintf(buf, "\x1b[M%c%c%c", 0, 32+x+1, 32+y+1);
1462 /* from urxvt */
1463 if (e->xbutton.type == MotionNotify) {
1464 if (!IS_SET(MODE_MOUSEMOTION) || (x == term->mouseox && y == term->mouseoy)) return;
1465 button = term->mouseob+32;
1466 term->mouseox = x;
1467 term->mouseoy = y;
1468 } else if (e->xbutton.type == ButtonRelease || button == AnyButton) {
1469 button = 3;
1470 } else {
1471 button -= Button1;
1472 if (button >= 3) button += 64-3;
1473 if (e->xbutton.type == ButtonPress) {
1474 term->mouseob = button;
1475 term->mouseox = x;
1476 term->mouseoy = y;
1479 buf[3] = 32+button+(state & ShiftMask ? 4 : 0)+(state & Mod4Mask ? 8 : 0)+(state & ControlMask ? 16 : 0);
1480 ttywrite(buf, 6);
1484 static void xfixsel (void) {
1485 if (lastSelStr != NULL) {
1486 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
1487 XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, xw.win, CurrentTime);
1488 } else {
1489 if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) == xw.win) XSetSelectionOwner(xw.dpy, XA_PRIMARY, None, CurrentTime);
1490 if (XGetSelectionOwner(xw.dpy, XA_CLIPBOARD) == xw.win) XSetSelectionOwner(xw.dpy, XA_CLIPBOARD, None, CurrentTime);
1492 XFlush(xw.dpy);
1496 static void xsetsel (char *str) {
1497 /* register the selection for both the clipboard and the primary */
1498 if (term == NULL) return;
1499 if (term->sel.clip != NULL) free(term->sel.clip);
1500 term->sel.clip = str;
1501 if (lastSelStr != NULL) free(lastSelStr);
1502 lastSelStr = (str != NULL ? strdup(str) : NULL);
1503 xfixsel();
1504 //fprintf(stderr, "[%s]\n", str);
1508 static Line selgetlinebyy (int y) {
1509 Line l;
1511 if (y >= term->row) return NULL;
1512 if (y < 0) {
1513 if (y <= -(term->maxhistory)) return NULL;
1514 l = term->line[term->row+(-y)-1];
1515 } else {
1516 l = term->line[y];
1518 return l;
1522 static void selcopy (void) {
1523 char *str, *ptr;
1524 int x, y, bufsize, is_selected = 0;
1526 if (term == NULL || term->sel.bx == -1) {
1527 str = NULL;
1528 } else {
1529 int sy = MIN(term->sel.e.y, term->sel.b.y);
1530 int ey = MAX(term->sel.e.y, term->sel.b.y);
1533 fprintf(stderr, "bx=%d; ex=%d; by=%d; ey=%d\n", term->sel.bx, term->sel.by, term->sel.ex, term->sel.ey);
1534 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);
1535 fprintf(stderr, " sy=%d; ey=%d\n", sy, ey);
1537 bufsize = (term->col+1)*(ey-sy+1)*UTF_SIZ;
1538 ptr = str = malloc(bufsize);
1539 /* append every set & selected glyph to the selection */
1540 for (y = sy; y <= ey; ++y) {
1541 Line l = selgetlinebyy(y);
1543 if (y >= term->row) break;
1544 if (l == NULL) continue;
1545 for (x = 0; x < term->col; ++x) {
1546 is_selected = selected(x, y);
1547 //if (is_selected) term->line[y][x].state |= GLYPH_DIRTY;
1548 if (/*(l[x].state & GLYPH_SET) &&*/ is_selected) {
1549 int size = utf8size(l[x].c);
1551 if (size == 1 && l[x].c[0] == 0) {
1552 *ptr = ' ';
1553 } else {
1554 memcpy(ptr, l[x].c, size);
1556 ptr += size;
1559 /* \n at the end of every selected line except for the last one */
1560 if (is_selected && y < ey) *ptr++ = '\n';
1562 *ptr = 0;
1564 xsetsel(str);
1568 static void selnotify (XEvent *e) {
1569 ulong nitems, ofs, rem;
1570 int format;
1571 uchar *data;
1572 Atom type;
1573 XSelectionEvent *se = (XSelectionEvent *)e;
1574 int isutf8;
1575 int wasbrk = 0;
1577 if (term == NULL) return;
1578 if (se->property != XA_VT_SELECTION) return;
1579 if (se->target == XA_UTF8) {
1580 isutf8 = 1;
1581 } else if (se->target == XA_STRING) {
1582 isutf8 = 0;
1583 } else {
1584 return;
1586 if (!isutf8) return;
1587 ofs = 0;
1588 do {
1589 if (XGetWindowProperty(xw.dpy, xw.win, se->property, ofs, BUFSIZ/4, False, AnyPropertyType, &type, &format, &nitems, &rem, &data)) {
1590 fprintf(stderr, "Clipboard allocation failed\n");
1591 return;
1593 //fprintf(stderr, "nitems=%d; format=%d; rem=%d\n", (int)nitems, format, (int)rem);
1594 if (term->cmdMode) {
1595 tcmdput((const char *)data, nitems*format/8);
1596 } else {
1597 if (nitems*format/8 > 0 && !wasbrk && IS_SET(MODE_BRACPASTE)) {
1598 wasbrk = 1;
1599 ttywritestr("\x1b[200~");
1601 ttywrite((const char *)data, nitems*format/8);
1603 XFree(data);
1604 /* number of 32-bit chunks returned */
1605 ofs += nitems*format/32;
1606 } while (rem > 0);
1608 if (wasbrk) ttywritestr("\x1b[201~");
1612 static void selpaste (Atom which) {
1613 if (term == NULL) return;
1614 if (XGetSelectionOwner(xw.dpy, which) != None) {
1615 XConvertSelection(xw.dpy, which, term->sel.xtarget, XA_VT_SELECTION, xw.win, CurrentTime);
1616 return;
1618 if (which == XA_PRIMARY) selpaste(XA_SECONDARY);
1619 else if (which == XA_SECONDARY) selpaste(XA_CLIPBOARD);
1623 static void selrequest (XEvent *e) {
1624 XSelectionRequestEvent *xsre;
1625 XSelectionEvent xev;
1627 if (lastSelStr == NULL) return;
1628 xsre = (XSelectionRequestEvent *)e;
1629 xev.type = SelectionNotify;
1630 xev.requestor = xsre->requestor;
1631 xev.selection = xsre->selection;
1632 xev.target = xsre->target;
1633 xev.time = xsre->time;
1634 /* reject */
1635 xev.property = None;
1636 if (xsre->target == XA_TARGETS) {
1637 /* respond with the supported type */
1638 Atom string = XA_UTF8;
1640 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_ATOM, 32, PropModeReplace, (uchar *)&string, 1);
1641 xev.property = xsre->property;
1642 } else if (xsre->target == XA_UTF8 && lastSelStr != NULL) {
1643 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_UTF8, 8, PropModeReplace, (uchar *)lastSelStr, strlen(lastSelStr));
1644 xev.property = xsre->property;
1645 } else if (xsre->target == XA_STRING && lastSelStr != NULL) {
1646 char *s = malloc(strlen(lastSelStr)*4+8);
1648 if (s != NULL) {
1649 int len = utf2loc(s, lastSelStr, strlen(lastSelStr));
1651 XChangeProperty(xsre->display, xsre->requestor, xsre->property, xsre->target, 8, PropModeReplace, (uchar *)s, len);
1652 xev.property = xsre->property;
1653 free(s);
1656 /* all done, send a notification to the listener */
1657 if (!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *)&xev)) fprintf(stderr, "Error sending SelectionNotify event\n");
1661 static void selclear (XEvent *e) {
1662 if (lastSelStr != NULL) free(lastSelStr);
1663 lastSelStr = NULL;
1664 if (term != NULL) {
1665 if (term->sel.clip != NULL) free(term->sel.clip);
1666 term->sel.clip = NULL;
1667 term->sel.mode = 0;
1668 term->sel.bx = -1;
1669 tfulldirt();
1670 draw(1);
1675 static void bpress (XEvent *e) {
1676 if (term == NULL) return;
1678 switch (opt_tabposition) {
1679 case 0: // bottom
1680 if (e->xbutton.y >= xw.h-xw.tabheight) return;
1681 break;
1682 case 1: // top
1683 if (e->xbutton.y < xw.tabheight) return;
1684 break;
1687 if ((e->xbutton.state&ShiftMask) != 0) {
1688 if (e->xbutton.button == Button1) {
1689 if (term->sel.bx != -1) tsetdirt(term->sel.b.y, term->sel.e.y);
1690 term->sel.mode = 1;
1691 term->sel.ex = term->sel.bx = X2COL(e->xbutton.x);
1692 term->sel.ey = term->sel.by = Y2ROW(e->xbutton.y);
1693 //fprintf(stderr, "x=%d; y=%d\n", term->sel.bx, term->sel.by);
1694 draw(1);
1695 return;
1698 if (e->xbutton.button == Button3) {
1699 term->sel.bx = -1;
1700 selcopy();
1701 draw(1);
1704 return;
1706 if (IS_SET(MODE_MOUSE)) mousereport(e);
1710 static void brelease (XEvent *e) {
1711 if (term == NULL) return;
1713 switch (opt_tabposition) {
1714 case 0: // bottom
1715 if (e->xbutton.y >= xw.h-xw.tabheight) return;
1716 break;
1717 case 1: // top
1718 if (e->xbutton.y < xw.tabheight) return;
1719 break;
1722 if ((e->xbutton.state&ShiftMask) == 0 && !term->sel.mode) {
1723 if (IS_SET(MODE_MOUSE)) mousereport(e);
1724 return;
1727 if (e->xbutton.button == Button2) {
1728 selpaste(XA_PRIMARY);
1729 } else if (e->xbutton.button == Button1) {
1730 term->sel.mode = 0;
1731 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey); // this sets sel.b and sel.e
1733 if (term->sel.bx == term->sel.ex && term->sel.by == term->sel.ey) {
1734 // single line, single char selection
1735 int now;
1737 markDirty(term->sel.ey, 2);
1738 term->sel.bx = -1;
1739 now = mclock_ticks();
1740 if (now-term->sel.tclick2 <= opt_tripleclick_timeout) {
1741 /* triple click on the line */
1742 term->sel.b.x = term->sel.bx = 0;
1743 term->sel.e.x = term->sel.ex = term->col;
1744 term->sel.b.y = term->sel.e.y = term->sel.ey;
1745 } else if (now-term->sel.tclick1 <= opt_doubleclick_timeout) {
1746 /* double click to select word */
1747 Line l = selgetlinebyy(term->sel.ey);
1749 if (l != NULL) {
1750 //FIXME: write better word selection code
1751 term->sel.bx = term->sel.ex;
1752 if (IS_GFX(l[term->sel.bx].mode)) {
1753 while (term->sel.bx > 0 && IS_GFX(l[term->sel.bx-1].mode)) --term->sel.bx;
1754 term->sel.b.x = term->sel.bx;
1755 while (term->sel.ex < term->col-1 && IS_GFX(l[term->sel.ex+1].mode)) ++term->sel.ex;
1756 } else {
1757 while (term->sel.bx > 0 && !IS_GFX(l[term->sel.bx-1].mode) && l[term->sel.bx-1].c[0] != ' ') --term->sel.bx;
1758 term->sel.b.x = term->sel.bx;
1759 while (term->sel.ex < term->col-1 && !IS_GFX(l[term->sel.ex+1].mode) && l[term->sel.ex+1].c[0] != ' ') ++term->sel.ex;
1761 term->sel.e.x = term->sel.ex;
1762 term->sel.b.y = term->sel.e.y = term->sel.ey;
1766 selcopy();
1767 draw(1);
1768 } else {
1769 // multiline or multichar selection
1770 selcopy();
1773 term->sel.tclick2 = term->sel.tclick1;
1774 term->sel.tclick1 = mclock_ticks();
1775 //draw(1);
1779 static void bmotion (XEvent *e) {
1780 if (term == NULL) return;
1782 switch (opt_tabposition) {
1783 case 0: // bottom
1784 if (e->xbutton.y >= xw.h-xw.tabheight) return;
1785 break;
1786 case 1: // top
1787 if (e->xbutton.y < xw.tabheight) return;
1788 break;
1791 if (term->sel.mode) {
1792 int oldey = term->sel.ey, oldex = term->sel.ex;
1794 getbuttoninfo(e, NULL, &term->sel.ex, &term->sel.ey); // this sets sel.b and sel.e
1795 if (oldey != term->sel.ey || oldex != term->sel.ex) {
1796 int starty = MIN(oldey, term->sel.ey);
1797 int endy = MAX(oldey, term->sel.ey);
1799 tsetdirt(starty, endy);
1800 draw(1);
1802 return;
1804 if (IS_SET(MODE_MOUSE)) mousereport(e);
1808 ////////////////////////////////////////////////////////////////////////////////
1809 // tty init
1810 static inline void setWantRedraw (void) { if (term != NULL) term->wantRedraw = 1; }
1814 static void dump (char c) {
1815 static int col;
1817 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
1818 if (++col % 10 == 0) fprintf(stderr, "\n");
1823 static __attribute__((noreturn)) void execsh (const char *str) {
1824 char **args;
1826 if (str == NULL) {
1827 char *envshell = getenv("SHELL");
1829 DEFAULT(envshell, opt_shell);
1830 setenv("TERM", opt_term, 1);
1831 args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL};
1832 } else {
1833 int argc = 0;
1835 args = calloc(32768, sizeof(char *));
1836 if (args == NULL) exit(EXIT_FAILURE);
1837 while (*str) {
1838 const char *b;
1840 while (*str && isspace(*str)) ++str;
1841 if (!str[0]) break;
1843 b = str;
1844 while (*str && !isspace(*str)) {
1845 if (*str++ == '\\') {
1846 if (*str) ++str;
1850 args[argc] = calloc(str-b+1, 1);
1851 memcpy(args[argc], b, str-b);
1854 FILE *fo = fopen("z.log", "a");
1855 fprintf(fo, "%d: [%s]\n", argc, args[argc]);
1856 fclose(fo);
1859 ++argc;
1861 if (argc < 1) exit(EXIT_FAILURE);
1863 execvp(args[0], args);
1864 exit(EXIT_FAILURE);
1868 static int ttynew (Term *term) {
1869 int m, s;
1870 struct winsize w = {term->row, term->col, 0, 0};
1871 static int signalset = 0;
1873 if (openpty(&m, &s, NULL, NULL, &w) < 0) die("openpty failed: %s", SERRNO);
1874 term->cmdfd = m;
1875 ttyresize();
1876 term->cmdfd = -1;
1877 switch (term->pid = fork()) {
1878 case -1: /* error */
1879 fprintf(stderr, "fork failed");
1880 return -1;
1881 case 0: /* child */
1882 setsid(); /* create a new process group */
1883 dup2(s, STDIN_FILENO);
1884 dup2(s, STDOUT_FILENO);
1885 dup2(s, STDERR_FILENO);
1886 if (ioctl(s, TIOCSCTTY, NULL) < 0) die("ioctl TIOCSCTTY failed: %s", SERRNO);
1887 close(s);
1888 close(m);
1889 execsh(term->execcmd);
1890 break;
1891 default: /* master */
1892 close(s);
1893 term->cmdfd = m;
1894 term->dead = 0;
1895 ttyresize();
1896 if (!signalset) { signalset = 1; signal(SIGCHLD, sigchld); }
1897 break;
1899 return 0;
1903 ////////////////////////////////////////////////////////////////////////////////
1904 // tty r/w
1905 static int ttycanread (void) {
1906 for (;;) {
1907 fd_set rfd;
1908 struct timeval timeout = {0};
1910 if (term->dead || term->cmdfd < 0) return 0;
1911 FD_ZERO(&rfd);
1912 FD_SET(term->cmdfd, &rfd);
1913 if (select(term->cmdfd+1, &rfd, NULL, NULL, &timeout) < 0) {
1914 if (errno == EINTR) continue;
1915 die("select failed: %s", SERRNO);
1917 if (FD_ISSET(term->cmdfd, &rfd)) return 1;
1918 break;
1920 return 0;
1924 static int ttycanwrite (void) {
1925 for (;;) {
1926 fd_set wfd;
1927 struct timeval timeout = {0};
1929 if (term->dead || term->cmdfd < 0) return 0;
1930 FD_ZERO(&wfd);
1931 FD_SET(term->cmdfd, &wfd);
1932 if (select(term->cmdfd+1, NULL, &wfd, NULL, &timeout) < 0) {
1933 if (errno == EINTR) continue;
1934 die("select failed: %s", SERRNO);
1936 if (FD_ISSET(term->cmdfd, &wfd)) return 1;
1937 break;
1939 return 0;
1943 #ifdef DUMP_IO
1944 static void wrstr (const char *s, int len) {
1945 if (s == NULL) return;
1946 while (len-- > 0) {
1947 unsigned char c = (unsigned char)(*s++);
1949 if (c < 32) fprintf(stderr, "{%u}", c); else fwrite(&c, 1, 1, stderr);
1952 #endif
1955 static void ttyread (void) {
1956 char *ptr;
1957 int left;
1959 /* append read bytes to unprocessed bytes */
1960 if (term == NULL || term->dead || term->cmdfd < 0) return;
1961 #ifdef DUMP_PROG_OUTPUT
1962 term->xobuflen = term->obuflen;
1963 #endif
1964 left = OBUFSIZ-term->obuflen;
1965 //dlogf("0: ttyread before: free=%d, used=%d", left, term->obuflen);
1966 while (left > 0 && ttycanread()) {
1967 int ret;
1969 //if ((ret = read(term->cmdfd, term->obuf+term->obuflen, 1)) < 0) die("Couldn't read from shell: %s", SERRNO);
1970 if ((ret = read(term->cmdfd, term->obuf+term->obuflen, left)) < 0) {
1971 //fprintf(stderr, "Warning: couldn't read from tty #%d: %s\n", termidx, SERRNO);
1972 break;
1974 term->obuflen += ret;
1975 left -= ret;
1977 //dlogf("1: ttyread after: free=%d, used=%d", left, term->obuflen);
1978 /* process every complete utf8 char */
1979 #ifdef DUMP_PROG_OUTPUT
1981 FILE *fo = fopen("zlogo.log", "ab");
1982 if (fo) {
1983 fwrite(term->obuf+term->xobuflen, term->obuflen-term->xobuflen, 1, fo);
1984 fclose(fo);
1987 #endif
1988 ptr = term->obuf;
1989 if (term->needConv) {
1990 // need conversion from locale to utf-8
1991 //fprintf(stderr, "buf: %d bytes\n", term->obuflen);
1992 while (term->obuflen > 0) {
1993 char obuf[UTF_SIZ+1];
1994 int len;
1996 len = loc2utf(obuf, ptr, 1);
1997 #ifdef DUMP_IO_READ
1999 fprintf(stderr, "rdc: [");
2000 wrstr(ptr, 1);
2001 fprintf(stderr, "] --> [");
2002 wrstr(obuf, len);
2003 fprintf(stderr, "]\n");
2004 fflush(stderr);
2006 #endif
2007 if (len > 0) {
2008 obuf[len] = 0;
2009 tputc(obuf);
2011 ++ptr;
2012 --term->obuflen;
2014 term->obuflen = 0;
2015 } else {
2016 // don't do any conversion
2017 while (term->obuflen >= UTF_SIZ || isfullutf8(ptr, term->obuflen)) {
2018 ulong utf8c;
2019 char s[UTF_SIZ+1];
2020 int charsize = utf8decode(ptr, &utf8c); /* returns size of utf8 char in bytes */
2021 int len;
2023 len = utf8encode(utf8c, s);
2024 #ifdef DUMP_IO_READ
2026 fprintf(stderr, "rdx: [");
2027 wrstr(s, len);
2028 fprintf(stderr, "]\n");
2029 fflush(stderr);
2031 #endif
2032 if (len > 0) {
2033 s[len] = 0;
2034 tputc(s);
2036 ptr += charsize;
2037 term->obuflen -= charsize;
2039 //dlogf("2: ttyread afterproc: used=%d", term->obuflen);
2041 /* keep any uncomplete utf8 char for the next call */
2042 if (term->obuflen > 0) memmove(term->obuf, ptr, term->obuflen);
2046 static void ttyflushwrbuf (void) {
2047 if (term == NULL || term->dead || term->cmdfd < 0) return;
2048 if (term->wrbufpos >= term->wrbufused) {
2049 term->wrbufpos = term->wrbufused = 0;
2050 return;
2052 //dlogf("0: ttyflushwrbuf before: towrite=%d", term->wrbufused-term->wrbufpos);
2053 while (term->wrbufpos < term->wrbufused && ttycanwrite()) {
2054 int ret;
2056 if ((ret = write(term->cmdfd, term->wrbuf+term->wrbufpos, term->wrbufused-term->wrbufpos)) == -1) {
2057 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2059 term->wrbufpos += ret;
2061 if (term->wrbufpos > 0) {
2062 int left = term->wrbufused-term->wrbufpos;
2064 if (left < 1) {
2065 // write buffer is empty
2066 term->wrbufpos = term->wrbufused = 0;
2067 } else {
2068 memmove(term->wrbuf, term->wrbuf+term->wrbufpos, left);
2069 term->wrbufpos = 0;
2070 term->wrbufused = left;
2073 //dlogf("1: ttyflushwrbuf after: towrite=%d", term->wrbufused-term->wrbufpos);
2077 // convert char to locale and write it
2078 static void ttywriterawchar (const char *s, int len) {
2079 char loc[16];
2080 int clen;
2082 if (s == NULL || len < 1) return;
2083 if (term->needConv) {
2084 if ((clen = utf2loc(loc, s, len)) < 1) return;
2085 } else {
2086 if ((clen = utf8size(s)) < 1) return;
2087 memmove(loc, s, clen);
2089 #ifdef DUMP_IO_WRITE
2091 fprintf(stderr, "wrc: [");
2092 wrstr(s, len);
2093 fprintf(stderr, "] --> [");
2094 wrstr(loc, clen);
2095 fprintf(stderr, "]\n");
2096 fflush(stderr);
2098 #endif
2100 while (term->wrbufused+clen >= term->wrbufsize) {
2101 //FIXME: make write buffer dynamic?
2102 // force write at least one char
2103 //dlogf("ttywrite: forced write");
2104 if (write(term->cmdfd, term->wrbuf+term->wrbufpos, 1) == -1) {
2105 //fprintf(stderr, "Warning: write error on tty #%d: %s\n", termidx, SERRNO);
2106 } else {
2107 ++term->wrbufpos;
2109 ttyflushwrbuf(); // make room for char
2111 memcpy(term->wrbuf+term->wrbufused, loc, clen);
2112 term->wrbufused += clen;
2116 static void ttywrite (const char *s, size_t n) {
2117 if (term == NULL || term->dead || term->cmdfd < 0) return;
2118 #ifdef DUMP_PROG_INPUT
2119 if (s != NULL && n > 0) {
2120 FILE *fo = fopen("zlogw.log", "ab");
2121 if (fo) {
2122 fwrite(s, n, 1, fo);
2123 fclose(fo);
2126 #endif
2127 //ttyflushwrbuf();
2128 if (s != NULL && n > 0) {
2129 while (n > 0) {
2130 unsigned char c = (unsigned char)(s[0]);
2132 if (term->ubufpos > 0 && isfullutf8(term->ubuf, term->ubufpos)) {
2133 // have complete char
2134 ttywriterawchar(term->ubuf, term->ubufpos);
2135 term->ubufpos = 0;
2136 continue;
2139 if (term->ubufpos == 0) {
2140 // new char
2141 if (c < 128) {
2142 ttywriterawchar(s, 1);
2143 } else if ((c&0xc0) == 0xc0) {
2144 // new utf-8 char
2145 term->ubuf[term->ubufpos++] = *s;
2146 } else {
2147 // ignore unsynced utf-8
2149 ++s;
2150 --n;
2151 continue;
2153 // char continues
2154 if (c < 128 || term->ubufpos >= UTF_SIZ || (c&0xc0) == 0xc0) {
2155 // discard previous utf-8, it's bad
2156 term->ubufpos = 0;
2157 continue;
2159 // collect
2160 term->ubuf[term->ubufpos++] = *s;
2161 ++s;
2162 --n;
2163 if (isfullutf8(term->ubuf, term->ubufpos)) {
2164 // have complete char
2165 ttywriterawchar(term->ubuf, term->ubufpos);
2166 term->ubufpos = 0;
2170 ttyflushwrbuf();
2174 ////////////////////////////////////////////////////////////////////////////////
2175 // tty resize ioctl
2176 static void ttyresize (void) {
2177 struct winsize w;
2179 if (term != NULL && term->cmdfd >= 0) {
2180 w.ws_row = term->row;
2181 w.ws_col = term->col;
2182 w.ws_xpixel = w.ws_ypixel = 0;
2183 if (ioctl(term->cmdfd, TIOCSWINSZ, &w) < 0) fprintf(stderr, "Warning: couldn't set window size: %s\n", SERRNO);
2184 setWantRedraw();
2189 ////////////////////////////////////////////////////////////////////////////////
2190 // tty utilities
2191 static void csidump (void) {
2192 printf("ESC");
2193 for (int f = 1; f < term->escseq.len; ++f) {
2194 uint c = (term->escseq.buf[f]&0xff);
2196 if (isprint(c)) putchar(c);
2197 else if (c == '\n') printf("(\\n)");
2198 else if (c == '\r') printf("(\\r)");
2199 else if (c == 0x1b) printf("(\\e)");
2200 else printf("(%02x)", c);
2202 putchar('\n');
2206 static void tsetdirt (int top, int bot) {
2207 LIMIT(top, 0, term->row-1);
2208 LIMIT(bot, 0, term->row-1);
2209 for (int y = top; y <= bot; ++y) markDirty(y, 2);
2213 static void tfulldirt (void) {
2214 tsetdirt(0, term->row-1);
2218 static void tmoveto (int x, int y) {
2219 LIMIT(x, 0, term->col-1);
2220 LIMIT(y, 0, term->row-1);
2221 term->c.state &= ~CURSOR_WRAPNEXT;
2222 if (term->c.x != x || term->c.y != y) {
2223 term->c.x = x;
2224 term->c.y = y;
2225 setWantRedraw();
2230 static void tclearregion (int x1, int y1, int x2, int y2) {
2231 int temp;
2233 if (x1 > x2) { temp = x1; x1 = x2; x2 = temp; }
2234 if (y1 > y2) { temp = y1; y1 = y2; y2 = temp; }
2235 LIMIT(x1, 0, term->col-1);
2236 LIMIT(x2, 0, term->col-1);
2237 LIMIT(y1, 0, term->row-1);
2238 LIMIT(y2, 0, term->row-1);
2239 for (int y = y1; y <= y2; ++y) {
2240 markDirty(y, (x1 <= 0 && x2 >= term->col-1) ? 2 : 1);
2241 for (int x = x1; x <= x2; ++x) {
2242 term->line[y][x].fg = term->c.attr.fg;
2243 term->line[y][x].bg = term->c.attr.bg;
2244 term->line[y][x].state = GLYPH_DIRTY;
2245 term->line[y][x].mode = ATTR_NULL|(term->c.attr.mode&(ATTR_DEFFG|ATTR_DEFBG));
2246 term->line[y][x].c[0] = ' ';
2247 if (selected(x, y)) selhide();
2253 static void tcursor (int mode) {
2254 if (mode == CURSOR_SAVE) {
2255 term->csaved = term->c;
2256 } else if (mode == CURSOR_LOAD) {
2257 term->c = term->csaved;
2258 tmoveto(term->c.x, term->c.y);
2259 setWantRedraw();
2264 static void treset (void) {
2265 Glyph g;
2267 term->c = (TCursor){{
2268 .mode = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG,
2269 .fg = 0,
2270 .bg = 0
2271 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
2272 term->c.attr.fg = term->deffg;
2273 term->c.attr.bg = term->defbg;
2275 g.state = GLYPH_DIRTY;
2276 g.mode = term->c.attr.mode;
2277 g.fg = term->c.attr.fg;
2278 g.bg = term->c.attr.bg;
2279 g.c[0] = ' ';
2280 g.c[1] = 0;
2282 term->top = 0;
2283 term->bot = term->row-1;
2284 term->mode = MODE_WRAP/* | MODE_MOUSEBTN*/;
2285 //tclearregion(0, 0, term->col-1, term->row-1);
2286 for (int y = 0; y < term->row; ++y) {
2287 markDirty(y, 2);
2288 for (int x = 0; x < term->col; ++x) term->alt[y][x] = term->line[y][x] = g;
2290 for (int y = term->row; y < term->linecount; ++y) {
2291 for (int x = 0; x < term->col; ++x) term->line[y][x] = g;
2293 tcursor(CURSOR_SAVE);
2294 term->topline = 0;
2295 tfulldirt();
2299 static int tinitialize (int col, int row) {
2300 //memset(term, 0, sizeof(Term));
2301 //term->needConv = needConversion ? 1 : 0;
2302 term->wrbufsize = WBUFSIZ;
2303 term->deffg = term->deffg;
2304 term->defbg = term->defbg;
2305 term->row = row;
2306 term->col = col;
2307 term->dirty = calloc(term->row, sizeof(*term->dirty));
2308 term->maxhistory = opt_maxhistory;
2309 term->linecount = term->maxhistory+term->row;
2310 term->line = calloc(term->linecount, sizeof(Line));
2311 term->alt = calloc(term->row, sizeof(Line));
2312 for (int y = 0; y < term->linecount; ++y) term->line[y] = calloc(term->col, sizeof(Glyph));
2313 for (int y = 0; y < term->row; ++y) term->alt[y] = calloc(term->col, sizeof(Glyph));
2314 /* setup screen */
2315 treset();
2316 return 1;
2320 static void tswapscreen (void) {
2321 selhide();
2322 for (int f = 0; f < term->row; ++f) {
2323 Line t = term->line[f];
2325 term->line[f] = term->alt[f];
2326 term->alt[f] = t;
2328 term->mode ^= MODE_ALTSCREEN;
2329 tfulldirt();
2333 //FIXME: will not work for history
2334 static void selscroll (int orig, int n) {
2335 if (term->sel.bx == -1) return;
2337 if (BETWEEN(term->sel.by, orig, term->bot) || BETWEEN(term->sel.ey, orig, term->bot)) {
2338 if ((term->sel.by += n) > term->bot || (term->sel.ey += n) < term->top) {
2339 term->sel.bx = -1;
2340 return;
2342 if (term->sel.by < term->top) {
2343 term->sel.by = term->top;
2344 term->sel.bx = 0;
2346 if (term->sel.ey > term->bot) {
2347 term->sel.ey = term->bot;
2348 term->sel.ex = term->col;
2350 term->sel.b.y = term->sel.by, term->sel.b.x = term->sel.bx;
2351 term->sel.e.y = term->sel.ey, term->sel.e.x = term->sel.ex;
2356 static void tscrolldown (int orig, int n) {
2357 Line temp;
2359 LIMIT(n, 0, term->bot-orig+1);
2360 //fprintf(stderr, "tscrolldown(%d, %d)\n", orig, n);
2361 tclearregion(0, term->bot-n+1, term->col-1, term->bot);
2362 for (int f = term->bot; f >= orig+n; --f) {
2363 temp = term->line[f];
2364 term->line[f] = term->line[f-n];
2365 term->line[f-n] = temp;
2366 markDirty(f, 2);
2367 markDirty(f-n, 2);
2369 selscroll(orig, n);
2373 static void tscrollup (int orig, int n) {
2374 Line temp;
2376 if (term == NULL) return;
2377 LIMIT(n, 0, term->bot-orig+1);
2378 if (n < 1) return;
2379 //fprintf(stderr, "tscrollup(%d, %d)\n", orig, n);
2380 if (!IS_SET(MODE_ALTSCREEN) && n == 1 && orig == 0 && term->bot >= term->row-1 && term->maxhistory > 0) {
2381 //FIXME: move selection instead of resetting
2382 Line l = term->line[term->linecount-1];
2384 for (int f = term->linecount-1; f > term->row; --f) term->line[f] = term->line[f-1];
2385 term->line[term->row] = l;
2386 for (int x = 0; x < term->col; ++x) l[x] = term->line[0][x];
2387 if (term->sel.clip != NULL) {
2388 //FIXME: scroll selection
2389 selhide();
2393 tclearregion(0, orig, term->col-1, orig+n-1);
2395 for (int f = orig; f <= term->bot-n; ++f) {
2396 temp = term->line[f];
2397 term->line[f] = term->line[f+n];
2398 term->line[f+n] = temp;
2399 markDirty(f, 2);
2400 markDirty(f+n, 2);
2402 selscroll(orig, -n);
2406 static void tnewline (int first_col) {
2407 int y = term->c.y;
2409 if (y == term->bot) tscrollup(term->top, 1); else ++y;
2410 tmoveto(first_col ? 0 : term->c.x, y);
2414 static void csiparse (void) {
2415 const char *p = term->escseq.buf;
2417 term->escseq.narg = 0;
2418 if (*p == '?') { term->escseq.priv = 1; ++p; }
2419 while (p < term->escseq.buf+term->escseq.len) {
2420 int n = term->escseq.arg[term->escseq.narg];
2422 for (; *p && isdigit(*p); ++p) n = n*10+(p[0]-'0');
2423 term->escseq.arg[term->escseq.narg] = n;
2425 if (*p == ';' && term->escseq.narg+1 < ESC_ARG_SIZ) {
2426 ++term->escseq.narg;
2427 ++p;
2428 } else {
2429 term->escseq.mode = *p;
2430 ++term->escseq.narg;
2431 break;
2437 static void tsetchar (const char *c) {
2438 char ub[UTF_SIZ+1];
2439 int rev = 0, gfx = 0;
2440 int x = term->c.x, y = term->c.y;
2442 if (!term->needConv && unimap != NULL) {
2443 ulong cc;
2445 utf8decode(c, &cc);
2446 if (cc >= 0 && cc <= 65535) {
2447 ushort uc = unimap[cc];
2449 if (uc) {
2450 if (uc == 127) {
2451 // inversed space
2452 rev = 1;
2453 ub[0] = ' ';
2454 //ub[1] = 0;
2455 } else {
2456 if (uc&0x8000) {
2457 ub[0] = (uc&0x7f);
2458 gfx = 1;
2459 } else {
2460 //ub[0] = uc&0x7f;
2461 utf8encode(uc, ub);
2464 c = ub;
2469 if (rev || gfx || (term->line[y][x].state & GLYPH_SET) == 0 || ATTRCMP(term->line[y][x], term->c.attr)) {
2470 term->line[y][x] = term->c.attr;
2471 if (rev) term->line[y][x].mode ^= ATTR_REVERSE;
2472 if (gfx) term->line[y][x].mode |= ATTR_GFX;
2473 } else {
2474 int clen = utf8size(c), olen = utf8size(term->line[y][x].c);
2476 if (clen < 1 || (clen == olen && memcmp(c, term->line[y][x].c, clen) == 0)) return;
2479 markDirty(y, 1);
2481 term->line[y][x] = term->c.attr;
2482 if (rev) term->line[y][x].mode ^= ATTR_REVERSE;
2483 if (gfx) {
2484 term->line[y][x].mode &= ~(ATTR_GFX|ATTR_GFX1);
2485 term->line[y][x].mode |= (term->line[y][x].mode&ATTR_G1) ? ATTR_GFX1 : ATTR_GFX;
2488 term->line[y][x].state |= (GLYPH_SET | GLYPH_DIRTY);
2489 memmove(term->line[y][x].c, c, UTF_SIZ);
2491 if (IS_GFX(term->line[y][x].mode)) {
2492 unsigned char c = (unsigned char)(term->line[y][x].c[0]);
2494 if (c > 95 && c < 128) term->line[y][x].c[0] -= 95;
2495 else if (c > 127) term->line[y][x].c[0] = ' ';
2497 if (term->sel.clip != NULL && selected(x, y)) {
2498 selhide();
2500 //dlogf("tsetchar(%d,%d): [%c] (%d); dirty=%d\n", x, y, c[0], term->wantRedraw, term->dirty[y]);
2504 static void tdeletechar (int n) {
2505 int src = term->c.x+n;
2506 int dst = term->c.x;
2507 int size = term->col-src;
2509 markDirty(term->c.y, 2);
2510 if (src >= term->col) {
2511 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2512 } else {
2513 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2514 tclearregion(term->col-n, term->c.y, term->col-1, term->c.y);
2519 static void tinsertblank (int n) {
2520 int src = term->c.x;
2521 int dst = src+n;
2522 int size = term->col-dst;
2524 markDirty(term->c.y, 2);
2525 if (dst >= term->col) {
2526 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2527 } else {
2528 memmove(&term->line[term->c.y][dst], &term->line[term->c.y][src], size*sizeof(Glyph));
2529 tclearregion(src, term->c.y, dst-1, term->c.y);
2534 static void tinsertblankline (int n) {
2535 if (term->c.y < term->top || term->c.y > term->bot) return;
2536 tscrolldown(term->c.y, n);
2540 static void tdeleteline (int n) {
2541 if (term->c.y < term->top || term->c.y > term->bot) return;
2542 tscrollup(term->c.y, n);
2546 static void tsetattr (int *attr, int l) {
2547 for (int f = 0; f < l; ++f) {
2548 switch (attr[f]) {
2549 case 0:
2550 term->c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
2551 term->c.attr.mode |= ATTR_DEFFG | ATTR_DEFBG;
2552 term->c.attr.fg = term->deffg;
2553 term->c.attr.bg = term->defbg;
2554 break;
2555 case 1:
2556 term->c.attr.mode |= ATTR_BOLD;
2557 break;
2558 case 4:
2559 term->c.attr.mode |= ATTR_UNDERLINE;
2560 break;
2561 case 7:
2562 term->c.attr.mode |= ATTR_REVERSE;
2563 break;
2564 case 22:
2565 term->c.attr.mode &= ~ATTR_BOLD;
2566 break;
2567 case 24:
2568 term->c.attr.mode &= ~ATTR_UNDERLINE;
2569 break;
2570 case 27:
2571 term->c.attr.mode &= ~ATTR_REVERSE;
2572 break;
2573 case 38:
2574 if (f+2 < l && attr[f+1] == 5) {
2575 f += 2;
2576 if (BETWEEN(attr[f], 0, 255)) {
2577 term->c.attr.fg = attr[f];
2578 term->c.attr.mode &= ~ATTR_DEFFG;
2579 } else {
2580 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[f]);
2582 } else {
2583 //fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2584 term->c.attr.fg = term->deffg;
2585 term->c.attr.mode |= ATTR_DEFFG;
2587 break;
2588 case 39:
2589 term->c.attr.fg = term->deffg;
2590 term->c.attr.mode |= ATTR_DEFFG;
2591 break;
2592 case 48:
2593 if (f+2 < l && attr[f+1] == 5) {
2594 f += 2;
2595 if (BETWEEN(attr[f], 0, 255)) {
2596 term->c.attr.bg = attr[f];
2597 term->c.attr.mode &= ~ATTR_DEFBG;
2598 } else {
2599 fprintf(stderr, "erresc: bad bgcolor %d\n", attr[f]);
2601 } else {
2602 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]);
2604 break;
2605 case 49:
2606 term->c.attr.bg = term->defbg;
2607 term->c.attr.mode |= ATTR_DEFBG;
2608 break;
2609 default:
2610 if (BETWEEN(attr[f], 30, 37)) { term->c.attr.fg = attr[f]-30; term->c.attr.mode &= ~ATTR_DEFFG; }
2611 else if (BETWEEN(attr[f], 40, 47)) { term->c.attr.bg = attr[f]-40; term->c.attr.mode &= ~ATTR_DEFBG; }
2612 else if (BETWEEN(attr[f], 90, 97)) { term->c.attr.fg = attr[f]-90+8; term->c.attr.mode &= ~ATTR_DEFFG; }
2613 else if (BETWEEN(attr[f], 100, 107)) { term->c.attr.bg = attr[f]-100+8; term->c.attr.mode &= ~ATTR_DEFBG; }
2614 else { fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[f]); csidump(); }
2615 break;
2621 static void tsetscroll (int t, int b) {
2622 int temp;
2624 LIMIT(t, 0, term->row-1);
2625 LIMIT(b, 0, term->row-1);
2626 if (t > b) {
2627 temp = t;
2628 t = b;
2629 b = temp;
2631 term->top = t;
2632 term->bot = b;
2636 ////////////////////////////////////////////////////////////////////////////////
2637 // esc processing
2638 static void csihandle (void) {
2639 switch (term->escseq.mode) {
2640 case '@': /* ICH -- Insert <n> blank char */
2641 DEFAULT(term->escseq.arg[0], 1);
2642 tinsertblank(term->escseq.arg[0]);
2643 break;
2644 case 'A': /* CUU -- Cursor <n> Up */
2645 case 'e':
2646 DEFAULT(term->escseq.arg[0], 1);
2647 tmoveto(term->c.x, term->c.y-term->escseq.arg[0]);
2648 break;
2649 case 'B': /* CUD -- Cursor <n> Down */
2650 DEFAULT(term->escseq.arg[0], 1);
2651 tmoveto(term->c.x, term->c.y+term->escseq.arg[0]);
2652 break;
2653 case 'C': /* CUF -- Cursor <n> Forward */
2654 case 'a':
2655 DEFAULT(term->escseq.arg[0], 1);
2656 tmoveto(term->c.x+term->escseq.arg[0], term->c.y);
2657 break;
2658 case 'D': /* CUB -- Cursor <n> Backward */
2659 DEFAULT(term->escseq.arg[0], 1);
2660 tmoveto(term->c.x-term->escseq.arg[0], term->c.y);
2661 break;
2662 case 'E': /* CNL -- Cursor <n> Down and first col */
2663 DEFAULT(term->escseq.arg[0], 1);
2664 tmoveto(0, term->c.y+term->escseq.arg[0]);
2665 break;
2666 case 'F': /* CPL -- Cursor <n> Up and first col */
2667 DEFAULT(term->escseq.arg[0], 1);
2668 tmoveto(0, term->c.y-term->escseq.arg[0]);
2669 break;
2670 case 'G': /* CHA -- Move to <col> */
2671 case '`': /* XXX: HPA -- same? */
2672 DEFAULT(term->escseq.arg[0], 1);
2673 tmoveto(term->escseq.arg[0]-1, term->c.y);
2674 break;
2675 case 'H': /* CUP -- Move to <row> <col> */
2676 case 'f': /* XXX: HVP -- same? */
2677 DEFAULT(term->escseq.arg[0], 1);
2678 DEFAULT(term->escseq.arg[1], 1);
2679 tmoveto(term->escseq.arg[1]-1, term->escseq.arg[0]-1);
2680 break;
2681 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
2682 case 'J': /* ED -- Clear screen */
2683 term->sel.bx = -1;
2684 switch (term->escseq.arg[0]) {
2685 case 0: /* below */
2686 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2687 if (term->c.y < term->row-1) tclearregion(0, term->c.y+1, term->col-1, term->row-1);
2688 break;
2689 case 1: /* above */
2690 if (term->c.y > 1) tclearregion(0, 0, term->col-1, term->c.y-1);
2691 tclearregion(0, term->c.y, term->c.x, term->c.y);
2692 break;
2693 case 2: /* all */
2694 tclearregion(0, 0, term->col-1, term->row-1);
2695 break;
2696 default:
2697 goto unknown;
2699 break;
2700 case 'K': /* EL -- Clear line */
2701 switch (term->escseq.arg[0]) {
2702 case 0: /* right */
2703 tclearregion(term->c.x, term->c.y, term->col-1, term->c.y);
2704 break;
2705 case 1: /* left */
2706 tclearregion(0, term->c.y, term->c.x, term->c.y);
2707 break;
2708 case 2: /* all */
2709 tclearregion(0, term->c.y, term->col-1, term->c.y);
2710 break;
2712 break;
2713 case 'S': /* SU -- Scroll <n> line up */
2714 DEFAULT(term->escseq.arg[0], 1);
2715 tscrollup(term->top, term->escseq.arg[0]);
2716 break;
2717 case 'T': /* SD -- Scroll <n> line down */
2718 DEFAULT(term->escseq.arg[0], 1);
2719 tscrolldown(term->top, term->escseq.arg[0]);
2720 break;
2721 case 'L': /* IL -- Insert <n> blank lines */
2722 DEFAULT(term->escseq.arg[0], 1);
2723 tinsertblankline(term->escseq.arg[0]);
2724 break;
2725 case 'l': /* RM -- Reset Mode */
2726 if (term->escseq.priv) {
2727 switch (term->escseq.arg[0]) {
2728 case 1: // 1001 for xterm compatibility
2729 DUMP_KEYPAD_SWITCH("1", "OFF");
2730 term->mode &= ~MODE_APPKEYPAD;
2731 break;
2732 case 5: /* DECSCNM -- Remove reverse video */
2733 if (IS_SET(MODE_REVERSE)) {
2734 term->mode &= ~MODE_REVERSE;
2735 tfulldirt();
2737 break;
2738 case 7: /* autowrap off */
2739 term->mode &= ~MODE_WRAP;
2740 break;
2741 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
2742 break;
2743 case 20: /* non-standard code? */
2744 term->mode &= ~MODE_CRLF;
2745 break;
2746 case 25: /* hide cursor */
2747 term->c.state |= CURSOR_HIDE;
2748 break;
2749 case 1000: /* disable X11 xterm mouse reporting */
2750 term->mode &= ~MODE_MOUSEBTN;
2751 break;
2752 case 1002:
2753 term->mode &= ~MODE_MOUSEMOTION;
2754 break;
2755 case 1004:
2756 term->mode &= ~MODE_FOCUSEVT;
2757 break;
2758 case 1049: /* = 1047 and 1048 */
2759 case 47:
2760 case 1047:
2761 if (IS_SET(MODE_ALTSCREEN)) {
2762 tclearregion(0, 0, term->col-1, term->row-1);
2763 tswapscreen();
2765 if (term->escseq.arg[0] != 1049) break;
2766 case 1048:
2767 tcursor(CURSOR_LOAD);
2768 break;
2769 case 2004: /* reset bracketed paste mode */
2770 term->mode &= ~MODE_BRACPASTE;
2771 break;
2772 default:
2773 goto unknown;
2775 } else {
2776 switch (term->escseq.arg[0]) {
2777 case 3:
2778 term->mode &= ~MODE_DISPCTRL;
2779 break;
2780 case 4:
2781 term->mode &= ~MODE_INSERT;
2782 break;
2783 default:
2784 goto unknown;
2787 break;
2788 case 'M': /* DL -- Delete <n> lines */
2789 DEFAULT(term->escseq.arg[0], 1);
2790 tdeleteline(term->escseq.arg[0]);
2791 break;
2792 case 'X': /* ECH -- Erase <n> char */
2793 DEFAULT(term->escseq.arg[0], 1);
2794 tclearregion(term->c.x, term->c.y, term->c.x + term->escseq.arg[0], term->c.y);
2795 break;
2796 case 'P': /* DCH -- Delete <n> char */
2797 DEFAULT(term->escseq.arg[0], 1);
2798 tdeletechar(term->escseq.arg[0]);
2799 break;
2800 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
2801 case 'd': /* VPA -- Move to <row> */
2802 DEFAULT(term->escseq.arg[0], 1);
2803 tmoveto(term->c.x, term->escseq.arg[0]-1);
2804 break;
2805 case 'h': /* SM -- Set terminal mode */
2806 if (term->escseq.priv) {
2807 switch (term->escseq.arg[0]) {
2808 case 1:
2809 DUMP_KEYPAD_SWITCH("1", "ON");
2810 term->mode |= MODE_APPKEYPAD;
2811 break;
2812 case 5: /* DECSCNM -- Reverve video */
2813 if (!IS_SET(MODE_REVERSE)) {
2814 term->mode |= MODE_REVERSE;
2815 tfulldirt();
2817 break;
2818 case 7:
2819 term->mode |= MODE_WRAP;
2820 break;
2821 case 20:
2822 term->mode |= MODE_CRLF;
2823 break;
2824 case 12: /* att610 -- Start blinking cursor (IGNORED) */
2825 /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
2826 if (term->escseq.narg > 1 && term->escseq.arg[1] != 25) break;
2827 case 25:
2828 term->c.state &= ~CURSOR_HIDE;
2829 break;
2830 case 1000: /* 1000,1002: enable xterm mouse report */
2831 term->mode |= MODE_MOUSEBTN;
2832 break;
2833 case 1002:
2834 term->mode |= MODE_MOUSEMOTION;
2835 break;
2836 case 1004:
2837 term->mode |= MODE_FOCUSEVT;
2838 break;
2839 case 1049: /* = 1047 and 1048 */
2840 case 47:
2841 case 1047:
2842 if (IS_SET(MODE_ALTSCREEN)) tclearregion(0, 0, term->col-1, term->row-1); else tswapscreen();
2843 if (term->escseq.arg[0] != 1049) break;
2844 case 1048:
2845 tcursor(CURSOR_SAVE);
2846 break;
2847 case 2004: /* set bracketed paste mode */
2848 term->mode |= MODE_BRACPASTE;
2849 break;
2850 default: goto unknown;
2852 } else {
2853 switch (term->escseq.arg[0]) {
2854 case 3:
2855 term->mode |= MODE_DISPCTRL;
2856 break;
2857 case 4:
2858 term->mode |= MODE_INSERT;
2859 break;
2860 default:
2861 goto unknown;
2864 break;
2865 case 'm': /* SGR -- Terminal attribute (color) */
2866 tsetattr(term->escseq.arg, term->escseq.narg);
2867 break;
2868 case 'n':
2869 if (!term->escseq.priv) {
2870 switch (term->escseq.arg[0]) {
2871 case 6: { /* cursor position report */
2872 char buf[32];
2874 sprintf(buf, "\x1b[%d;%dR", term->c.x+1, term->c.y+1);
2875 ttywritestr(buf);
2876 } break;
2879 break;
2880 case 'r': /* DECSTBM -- Set Scrolling Region */
2881 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
2882 // xterm compatibility
2883 DUMP_KEYPAD_SWITCH("1001", "OFF");
2884 term->mode &= ~MODE_APPKEYPAD;
2885 } else if (term->escseq.priv) {
2886 goto unknown;
2887 } else {
2888 DEFAULT(term->escseq.arg[0], 1);
2889 DEFAULT(term->escseq.arg[1], term->row);
2890 tsetscroll(term->escseq.arg[0]-1, term->escseq.arg[1]-1);
2891 tmoveto(0, 0);
2893 break;
2894 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
2895 if (term->escseq.priv && term->escseq.arg[0] == 1001) {
2896 // xterm compatibility
2897 DUMP_KEYPAD_SWITCH("1001", "ON");
2898 term->mode |= MODE_APPKEYPAD;
2899 } else {
2900 tcursor(CURSOR_SAVE);
2902 break;
2903 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
2904 tcursor(CURSOR_LOAD);
2905 break;
2906 default:
2907 unknown:
2908 fprintf(stderr, "erresc: unknown csi ");
2909 csidump();
2910 break;
2915 static void csireset (void) {
2916 memset(&term->escseq, 0, sizeof(term->escseq));
2920 static void tputtab (void) {
2921 int space = opt_tabsize-term->c.x%opt_tabsize;
2923 if (space > 0) tmoveto(term->c.x+space, term->c.y);
2927 ////////////////////////////////////////////////////////////////////////////////
2928 // put char to output buffer or process command
2930 // return 1 if this was control character
2931 // return -1 if this should break esape sequence
2932 static int tputc_ctrl (char ascii) {
2933 int res = 1;
2935 if (term->esc&ESC_TITLE) return 0;
2937 switch (ascii) {
2938 case '\t': tputtab(); break;
2939 case '\b': tmoveto(term->c.x-1, term->c.y); break;
2940 case '\r': tmoveto(0, term->c.y); break;
2941 case '\f': case '\n': case '\v': tnewline(IS_SET(MODE_CRLF)); break; /* go to first col if the mode is set */
2942 case '\a': if (!(xw.state & WIN_FOCUSED)) xseturgency(1); if (term->audiblebell) XBell(xw.dpy, 100); break;
2943 case 14: term->c.attr.mode |= ATTR_G1; break;
2944 case 15: term->c.attr.mode &= ~ATTR_G1; break;
2945 case 0x18: case 0x1a: res = -1; break; // do nothing, interrupt current escape sequence
2946 case 127: break; // ignore it
2947 case '\033': csireset(); term->esc = ESC_START; break;
2948 //case 0x9b: csireset(); term->esc = ESC_START | ESC_CSI; break;
2949 default: res = 0; break;
2951 return res;
2955 static void tputc (const char *c) {
2956 char ascii = *c;
2957 int ctl = tputc_ctrl(ascii);
2959 if (ctl > 0) return; // control char; should not break escape sequence
2960 if (ctl < 0) {
2961 // control char; should break escape sequence
2962 term->esc = 0;
2963 return;
2965 //dlogf("tputc: [%c]\n", c[0]);
2966 if (term->esc & ESC_START) {
2967 if (term->esc & ESC_CSI) {
2968 term->escseq.buf[term->escseq.len++] = ascii;
2969 if (BETWEEN(ascii, 0x40, 0x7E) || term->escseq.len >= ESC_BUF_SIZ) {
2970 term->esc = 0;
2971 csiparse();
2972 csihandle();
2974 } else if (term->esc & ESC_OSC) {
2975 /* TODO: handle other OSC */
2976 if (ascii == ';') {
2977 term->title[0] = 0;
2978 term->titlelen = 0;
2979 term->esc = ESC_START | ESC_TITLE;
2980 //updateTabBar = 1;
2982 } else if (term->esc & ESC_TITLE) {
2983 int len = utf8size(c);
2985 if (ascii == '\a' || term->titlelen+len >= ESC_TITLE_SIZ) {
2986 term->esc = 0;
2987 term->title[term->titlelen] = '\0';
2988 fixWindowTitle(term);
2989 updateTabBar = 1;
2990 } else if (len > 0) {
2991 memcpy(term->title+term->titlelen, c, len);
2992 term->titlelen += len;
2993 term->title[term->titlelen] = '\0';
2995 } else if (term->esc & ESC_ALTCHARSET) {
2996 term->esc = 0;
2997 switch (ascii) {
2998 case '0': /* Line drawing crap */
2999 term->c.attr.mode |= ATTR_GFX;
3000 break;
3001 case 'B': /* Back to regular text */
3002 term->c.attr.mode &= ~ATTR_GFX;
3003 break;
3004 default:
3005 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
3006 term->c.attr.mode &= ~ATTR_GFX;
3007 break;
3009 } else if (term->esc & ESC_ALTG1) {
3010 term->esc = 0;
3011 switch (ascii) {
3012 case '0': /* Line drawing crap */
3013 term->c.attr.mode |= ATTR_GFX1;
3014 break;
3015 case 'B': /* Back to regular text */
3016 term->c.attr.mode &= ~ATTR_GFX1;
3017 break;
3018 default:
3019 fprintf(stderr, "esc unhandled charset: ESC ) %c\n", ascii);
3020 term->c.attr.mode &= ~ATTR_GFX1;
3021 break;
3023 } else if (term->esc & ESC_HASH) {
3024 term->esc = 0;
3025 switch (ascii) {
3026 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
3027 //tfillscreenwithE();
3028 break;
3030 } else if (term->esc & ESC_PERCENT) {
3031 term->esc = 0;
3032 } else {
3033 switch (ascii) {
3034 case '[': term->esc |= ESC_CSI; break;
3035 case ']': term->esc |= ESC_OSC; break;
3036 case '(': term->esc |= ESC_ALTCHARSET; break;
3037 case ')': term->esc |= ESC_ALTG1; break;
3038 case '#': term->esc |= ESC_HASH; break;
3039 case '%': term->esc |= ESC_PERCENT; break;
3040 case 'D': /* IND -- Linefeed */
3041 term->esc = 0;
3042 if (term->c.y == term->bot) tscrollup(term->top, 1); else tmoveto(term->c.x, term->c.y+1);
3043 break;
3044 case 'E': /* NEL -- Next line */
3045 term->esc = 0;
3046 tnewline(1); /* always go to first col */
3047 break;
3048 case 'M': /* RI -- Reverse linefeed */
3049 term->esc = 0;
3050 if (term->c.y == term->top) tscrolldown(term->top, 1); else tmoveto(term->c.x, term->c.y-1);
3051 break;
3052 case 'c': /* RIS -- Reset to inital state */
3053 term->esc = 0;
3054 treset();
3055 break;
3056 case '=': /* DECPAM -- Application keypad */
3057 DUMP_KEYPAD_SWITCH("=", "ON");
3058 term->esc = 0;
3059 term->mode |= MODE_APPKEYPAD;
3060 break;
3061 case '>': /* DECPNM -- Normal keypad */
3062 DUMP_KEYPAD_SWITCH(">", "OFF");
3063 term->esc = 0;
3064 term->mode &= ~MODE_APPKEYPAD;
3065 break;
3066 case '7': /* DECSC -- Save Cursor */
3067 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
3068 //TODO?
3069 term->esc = 0;
3070 tcursor(CURSOR_SAVE);
3071 break;
3072 case '8': /* DECRC -- Restore Cursor */
3073 //TODO?
3074 term->esc = 0;
3075 tcursor(CURSOR_LOAD);
3076 break;
3077 case 'Z': /* DEC private identification */
3078 term->esc = 0;
3079 ttywritestr("\x1b[?1;2c");
3080 break;
3081 default:
3082 term->esc = 0;
3083 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X ('%c')\n", (uchar)ascii, isprint(ascii)?ascii:'.');
3084 break;
3087 } else {
3088 //if (term->sel.bx != -1 && BETWEEN(term->c.y, term->sel.by, term->sel.ey)) term->sel.bx = -1;
3089 do {
3090 if (term->needConv && IS_GFX(term->c.attr.mode)) {
3091 ulong cc;
3093 utf8decode(c, &cc);
3094 if (cc < 32 || cc >= 127) break; //FIXME: nothing at all
3095 } else {
3096 if ((unsigned char)ascii < 32 || ascii == 127) break; // seems that this chars are empty too
3098 if (IS_SET(MODE_WRAP) && (term->c.state&CURSOR_WRAPNEXT)) tnewline(1); // always go to first col
3099 tsetchar(c);
3100 if (term->c.x+1 < term->col) tmoveto(term->c.x+1, term->c.y); else term->c.state |= CURSOR_WRAPNEXT;
3101 } while (0);
3106 static void tunshowhistory (void) {
3107 if (term != NULL && term->topline != 0) {
3108 term->topline = 0;
3109 term->wantRedraw = 1;
3110 term->lastDrawTime = 0;
3115 static void tsendfocusevent (int focused) {
3116 if (term != NULL && IS_SET(MODE_FOCUSEVT)) {
3117 ttywritestr("\x1b[");
3118 ttywrite(focused?"I":"O", 1);
3123 static void tcmdlinefixofs (void) {
3124 int ofs, len;
3126 len = utf8strlen(term->cmdline);
3127 ofs = len-(term->col-1);
3128 if (ofs < 0) ofs = 0;
3129 for (term->cmdofs = 0; ofs > 0; --ofs) term->cmdofs += utf8size(term->cmdline+term->cmdofs);
3130 //markDirty(term->row-1, 2);
3131 markDirty(term->row-term->topline-1, 2);
3135 static void tcmdlineinit (void) {
3136 term->cmdMode = 1;
3137 term->cmdofs = 0;
3138 term->cmdline[0] = 0;
3139 term->cmdc[0] = 0;
3140 term->cmdcl = 0;
3141 tcmdlinefixofs();
3145 static void tcmdlinehide (void) {
3146 term->cmdMode = 0;
3147 markDirty(term->row-term->topline-1, 2);
3151 // utf-8
3152 static void tcmdaddchar (const char *s) {
3153 int len = utf8size(s);
3155 if (len > 0) {
3156 int slen = strlen(term->cmdline);
3158 if (slen+len < sizeof(term->cmdline)) {
3159 memcpy(term->cmdline+slen, s, len);
3160 term->cmdline[slen+len] = 0;
3161 tcmdlinefixofs();
3167 static void tcmdput (const char *s, int len) {
3168 while (len-- > 0) {
3169 int ok;
3171 term->cmdc[term->cmdcl++] = *s++;
3172 term->cmdc[term->cmdcl] = 0;
3174 if ((ok = isfullutf8(term->cmdc, term->cmdcl)) != 0 || term->cmdcl == UTF_SIZ) {
3175 if (ok) tcmdaddchar(term->cmdc);
3176 term->cmdcl = 0;
3182 ////////////////////////////////////////////////////////////////////////////////
3183 // tty resising
3184 static int tresize (int col, int row) {
3185 int mincol = MIN(col, term->col);
3186 int slide = term->c.y-row+1;
3187 Glyph g;
3189 if (col < 1 || row < 1) return 0;
3191 g.state = GLYPH_DIRTY;
3192 g.mode = ATTR_NULL|ATTR_DEFFG|ATTR_DEFBG;
3193 g.fg = term->deffg;
3194 g.bg = term->defbg;
3195 g.c[0] = ' ';
3196 g.c[1] = 0;
3198 if (slide > 0) {
3199 tsetscroll(0, term->row-1);
3200 for (; slide > 0; --slide) tscrollup(0, 1); // to fill history
3203 if (row < term->row) {
3204 /* free unneeded rows */
3205 for (int f = row; f < term->row; ++f) free(term->alt[f]);
3206 for (int f = term->linecount-(term->row-row); f < term->linecount; ++f) free(term->line[f]);
3207 term->linecount -= (term->row-row);
3208 /* resize to new height */
3209 term->alt = realloc(term->alt, row*sizeof(Line));
3210 term->line = realloc(term->line, term->linecount*sizeof(Line));
3211 } else if (row > term->row) {
3212 /* resize to new height */
3213 term->alt = realloc(term->alt, row*sizeof(Line));
3214 term->line = realloc(term->line, (row+term->maxhistory)*sizeof(Line));
3215 /* add more lines */
3216 for (int f = term->row; f < row; ++f) {
3217 term->alt[f] = calloc(col, sizeof(Glyph));
3218 for (int x = 0; x < col; ++x) term->alt[f][x] = g;
3220 for (int f = 0; f < row-term->row; ++f) {
3221 int y = term->linecount++;
3223 term->line[y] = calloc(col, sizeof(Glyph));
3224 for (int x = 0; x < col; ++x) term->line[y][x] = g;
3228 if (row != term->row) {
3229 term->dirty = realloc(term->dirty, row*sizeof(*term->dirty));
3232 /* resize each row to new width, zero-pad if needed */
3233 for (int f = 0; f < term->linecount; ++f) {
3234 term->line[f] = realloc(term->line[f], col*sizeof(Glyph));
3235 for (int x = mincol; x < col; ++x) term->line[f][x] = g;
3236 if (f < row) {
3237 markDirty(f, 2);
3238 term->alt[f] = realloc(term->alt[f], col*sizeof(Glyph));
3239 for (int x = mincol; x < col; ++x) term->alt[f][x] = g;
3242 /* update terminal size */
3243 term->topline = 0;
3244 term->col = col;
3245 term->row = row;
3246 /* make use of the LIMIT in tmoveto */
3247 tmoveto(term->c.x, term->c.y);
3248 /* reset scrolling region */
3249 tsetscroll(0, row-1);
3250 tfulldirt();
3251 return (slide > 0);
3255 static void xresize (int col, int row) {
3256 Pixmap newbuf;
3257 int oldw, oldh;
3259 if (term == NULL) return;
3260 oldw = term->picbufw;
3261 oldh = term->picbufh;
3262 term->picbufw = MAX(1, col*xw.cw);
3263 term->picbufh = MAX(1, row*xw.ch);
3264 newbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3265 XCopyArea(xw.dpy, term->picbuf, newbuf, dc.gc, 0, 0, term->picbufw, term->picbufh, 0, 0);
3266 XFreePixmap(xw.dpy, term->picbuf);
3267 XSetForeground(xw.dpy, dc.gc, dc.col[term->defbg]);
3268 if (term->picbufw > oldw) {
3269 XFillRectangle(xw.dpy, newbuf, dc.gc, oldw, 0, term->picbufw-oldw, MIN(term->picbufh, oldh));
3270 } else if (term->picbufw < oldw && xw.w > term->picbufw) {
3271 XClearArea(xw.dpy, xw.win, term->picbufw, 0, xw.w-term->picbufh, MIN(term->picbufh, oldh), False);
3273 if (term->picbufh > oldh) {
3274 XFillRectangle(xw.dpy, newbuf, dc.gc, 0, oldh, term->picbufw, term->picbufh-oldh);
3275 } else if (term->picbufh < oldh && xw.h > term->picbufh) {
3276 XClearArea(xw.dpy, xw.win, 0, term->picbufh, xw.w, xw.h-term->picbufh, False);
3278 term->picbuf = newbuf;
3279 tfulldirt();
3280 updateTabBar = 1;
3284 ////////////////////////////////////////////////////////////////////////////////
3285 // x11 drawing and utils
3286 static void xloadcols (void) {
3287 int f, r, g, b;
3288 XColor color;
3289 ulong white = WhitePixel(xw.dpy, xw.scr);
3291 dc.col = calloc(MAX_COLOR+1, sizeof(dc.col[0]));
3292 for (f = 0; f <= MAX_COLOR; ++f) dc.col[f] = white;
3293 /* load colors [0-15] */
3294 for (f = 0; f <= 15; ++f) {
3295 const char *cname = opt_colornames[f]!=NULL?opt_colornames[f]:defcolornames[f];
3297 if (!XAllocNamedColor(xw.dpy, xw.cmap, cname, &color, &color)) {
3298 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, cname);
3299 } else {
3300 dc.col[f] = color.pixel;
3303 /* load colors [256-...] */
3304 for (f = 256; f <= MAX_COLOR; ++f) {
3305 const char *cname = opt_colornames[f];
3307 if (cname == NULL) {
3308 if (LEN(defextcolornames) <= f-256) continue;
3309 cname = defextcolornames[f-256];
3311 if (cname == NULL) continue;
3312 if (!XAllocNamedColor(xw.dpy, xw.cmap, cname, &color, &color)) {
3313 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, cname);
3314 } else {
3315 dc.col[f] = color.pixel;
3318 /* load colors [16-255] ; same colors as xterm */
3319 for (f = 16, r = 0; r < 6; ++r) {
3320 for (g = 0; g < 6; ++g) {
3321 for (b = 0; b < 6; ++b) {
3322 if (opt_colornames[f] != NULL) {
3323 if (!XAllocNamedColor(xw.dpy, xw.cmap, opt_colornames[f], &color, &color)) {
3324 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, opt_colornames[f]);
3325 } else {
3326 dc.col[f] = color.pixel;
3328 } else {
3329 color.red = r == 0 ? 0 : 0x3737+0x2828*r;
3330 color.green = g == 0 ? 0 : 0x3737+0x2828*g;
3331 color.blue = b == 0 ? 0 : 0x3737+0x2828*b;
3332 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3333 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3334 } else {
3335 dc.col[f] = color.pixel;
3338 ++f;
3342 for (r = 0; r < 24; ++r, ++f) {
3343 if (opt_colornames[f] != NULL) {
3344 if (!XAllocNamedColor(xw.dpy, xw.cmap, opt_colornames[f], &color, &color)) {
3345 fprintf(stderr, "WARNING: could not allocate color #%d: '%s'\n", f, opt_colornames[f]);
3346 } else {
3347 dc.col[f] = color.pixel;
3349 } else {
3350 color.red = color.green = color.blue = 0x0808+0x0a0a*r;
3351 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
3352 fprintf(stderr, "WARNING: could not allocate color #%d\n", f);
3353 } else {
3354 dc.col[f] = color.pixel;
3359 for (int f = 0; f < LEN(opt_colornames); ++f) if (opt_colornames[f]) free(opt_colornames[f]);
3363 static void xclear (int x1, int y1, int x2, int y2) {
3364 XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? term->deffg : term->defbg]);
3365 XFillRectangle(xw.dpy, term->picbuf, dc.gc, x1*xw.cw, y1*xw.ch, (x2-x1+1)*xw.cw, (y2-y1+1)*xw.ch);
3369 static void xhints (void) {
3370 XClassHint class = {opt_class, opt_title};
3371 XWMHints wm = {.flags = InputHint, .input = 1};
3372 XSizeHints size = {
3373 .flags = PSize | PResizeInc | PBaseSize,
3374 .height = xw.h,
3375 .width = xw.w,
3376 .height_inc = xw.ch,
3377 .width_inc = xw.cw,
3378 .base_height = xw.h/*xw.tabheight*/,
3379 .base_width = xw.w,
3381 //XSetWMNormalHints(xw.dpy, xw.win, &size);
3382 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, &size, &wm, &class);
3386 static XFontSet xinitfont (const char *fontstr) {
3387 XFontSet set;
3388 char *def, **missing;
3389 int n;
3391 missing = NULL;
3392 set = XCreateFontSet(xw.dpy, fontstr, &missing, &n, &def);
3393 if (missing) {
3394 while (n--) fprintf(stderr, "sterm: missing fontset: %s\n", missing[n]);
3395 XFreeStringList(missing);
3397 return set;
3401 static void xgetfontinfo (XFontSet set, int *ascent, int *descent, short *lbearing, short *rbearing, Font *fid) {
3402 XFontStruct **xfonts;
3403 char **font_names;
3404 int n;
3406 *ascent = *descent = *lbearing = *rbearing = 0;
3407 n = XFontsOfFontSet(set, &xfonts, &font_names);
3408 for (int f = 0; f < n; ++f) {
3409 if (f == 0) *fid = (*xfonts)->fid;
3410 *ascent = MAX(*ascent, (*xfonts)->ascent);
3411 *descent = MAX(*descent, (*xfonts)->descent);
3412 *lbearing = MAX(*lbearing, (*xfonts)->min_bounds.lbearing);
3413 *rbearing = MAX(*rbearing, (*xfonts)->max_bounds.rbearing);
3414 ++xfonts;
3419 static void initfonts (const char *fontstr, const char *bfontstr, const char *tabfont) {
3420 if ((dc.font[0].set = xinitfont(fontstr)) == NULL) {
3421 if ((dc.font[0].set = xinitfont(FONT)) == NULL) die("can't load font %s", fontstr);
3423 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);
3425 if ((dc.font[1].set = xinitfont(bfontstr)) == NULL) {
3426 if ((dc.font[1].set = xinitfont(FONTBOLD)) == NULL) die("can't load font %s", bfontstr);
3428 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);
3430 if ((dc.font[2].set = xinitfont(tabfont)) == NULL) {
3431 if ((dc.font[2].set = xinitfont(FONTTAB)) == NULL) die("can't load font %s", tabfont);
3433 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);
3437 static void xinit (void) {
3438 XSetWindowAttributes attrs;
3439 Window parent;
3440 XColor blackcolor = { 0, 0, 0, 0, 0, 0 };
3442 if (!(xw.dpy = XOpenDisplay(NULL))) die("can't open display");
3444 XA_VT_SELECTION = XInternAtom(xw.dpy, "_STERM_SELECTION_", 0);
3445 XA_CLIPBOARD = XInternAtom(xw.dpy, "CLIPBOARD", 0);
3446 XA_UTF8 = XInternAtom(xw.dpy, "UTF8_STRING", 0);
3447 XA_NETWM_NAME = XInternAtom(xw.dpy, "_NET_WM_NAME", 0);
3448 XA_TARGETS = XInternAtom(xw.dpy, "TARGETS", 0);
3449 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
3451 xw.scr = XDefaultScreen(xw.dpy);
3452 /* font */
3453 initfonts(opt_fontnorm, opt_fontbold, opt_fonttab);
3454 /* XXX: Assuming same size for bold font */
3455 xw.cw = dc.font[0].rbearing-dc.font[0].lbearing;
3456 xw.ch = dc.font[0].ascent+dc.font[0].descent;
3457 xw.tch = dc.font[2].ascent+dc.font[2].descent;
3458 xw.tabheight = opt_disabletabs ? 0 : xw.tch+2;
3459 //fprintf(stderr, "tabh: %d\n", xw.tabheight);
3460 //xw.tabheight = 0;
3461 /* colors */
3462 xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
3463 xloadcols();
3464 /* window - default size */
3465 term->picbufh = term->row*xw.ch;
3466 term->picbufw = term->col*xw.cw;
3468 xw.h = term->picbufh+xw.tabheight;
3469 xw.w = term->picbufw;
3471 attrs.background_pixel = dc.col[defaultBG];
3472 attrs.border_pixel = dc.col[defaultBG];
3473 attrs.bit_gravity = NorthWestGravity;
3474 attrs.event_mask = FocusChangeMask | KeyPressMask
3475 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
3476 | /*ButtonMotionMask*/ PointerMotionMask | ButtonPressMask | ButtonReleaseMask
3477 | EnterWindowMask | LeaveWindowMask;
3478 attrs.colormap = xw.cmap;
3479 //fprintf(stderr, "oe: [%s]\n", opt_embed);
3480 parent = opt_embed ? strtol(opt_embed, NULL, 0) : XRootWindow(xw.dpy, xw.scr);
3481 xw.win = XCreateWindow(xw.dpy, parent, 0, 0,
3482 xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
3483 XDefaultVisual(xw.dpy, xw.scr),
3484 CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask
3485 | CWColormap,
3486 &attrs);
3487 xhints();
3488 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
3489 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
3490 /* input methods */
3491 if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) die("XOpenIM() failed");
3492 xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL);
3493 /* gc */
3494 dc.gc = XCreateGC(xw.dpy, xw.win, 0, NULL);
3495 /* white cursor, black outline */
3496 xw.cursor = XCreateFontCursor(xw.dpy, XC_xterm);
3497 XDefineCursor(xw.dpy, xw.win, xw.cursor);
3498 XRecolorCursor(xw.dpy, xw.cursor,
3499 &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff},
3500 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
3501 fixWindowTitle(term);
3502 //XStoreName(xw.dpy, xw.win, opt_title);
3504 XSetForeground(xw.dpy, dc.gc, 0);
3505 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
3506 if (xw.tabheight > 0) XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
3508 XMapWindow(xw.dpy, xw.win);
3510 #if BLANKPTR_USE_GLYPH_CURSOR
3511 xw.blankPtr = XCreateGlyphCursor(xw.dpy, dc.font[0].fid, dc.font[0].fid, ' ', ' ', &blackcolor, &blackcolor);
3512 #else
3513 static const char cmbmp[1] = {0};
3514 Pixmap pm;
3516 pm = XCreateBitmapFromData(xw.dpy, xw.win, cmbmp, 1, 1);
3517 xw.blankPtr = XCreatePixmapCursor(xw.dpy, pm, pm, &blackcolor, &blackcolor, 0, 0);
3518 XFreePixmap(xw.dpy, pm);
3519 #endif
3521 XSync(xw.dpy, 0);
3525 static void xblankPointer (void) {
3526 if (!ptrBlanked && xw.blankPtr != None) {
3527 ptrBlanked = 1;
3528 XDefineCursor(xw.dpy, xw.win, xw.blankPtr);
3529 XFlush(xw.dpy);
3534 static void xunblankPointer (void) {
3535 if (ptrBlanked && xw.cursor != None) {
3536 ptrBlanked = 0;
3537 XDefineCursor(xw.dpy, xw.win, xw.cursor);
3538 XFlush(xw.dpy);
3539 ptrLastMove = mclock_ticks();
3544 static void xdraws (const char *s, const Glyph *base, int x, int y, int charlen, int bytelen) {
3545 int fg = base->fg, bg = base->bg, temp;
3546 int winx = x*xw.cw, winy = y*xw.ch+dc.font[0].ascent, width = charlen*xw.cw;
3547 XFontSet fontset = dc.font[0].set;
3548 int defF = base->mode&ATTR_DEFFG, defB = base->mode&ATTR_DEFBG;
3550 /* only switch default fg/bg if term is in RV mode */
3551 if (IS_SET(MODE_REVERSE)) {
3552 if (defF) fg = term->defbg;
3553 if (defB) bg = term->deffg;
3555 if (base->mode&ATTR_REVERSE) defF = defB = 0;
3556 if (base->mode & ATTR_BOLD) {
3557 if (defF && defB && defaultBoldFG >= 0) fg = defaultBoldFG;
3558 else if (fg < 8) fg += 8;
3559 fontset = dc.font[1].set;
3561 if (base->mode & ATTR_UNDERLINE && defaultUnderlineFG >= 0) {
3562 if (defF && defB) fg = defaultUnderlineFG;
3565 if (base->mode&ATTR_REVERSE) { temp = fg; fg = bg; bg = temp; }
3567 XSetBackground(xw.dpy, dc.gc, dc.col[bg]);
3568 XSetForeground(xw.dpy, dc.gc, dc.col[fg]);
3572 FILE *fo = fopen("zlog.log", "ab");
3573 fprintf(fo, "xdraws: x=%d; y=%d; charlen=%d; bytelen=%d\n", x, y, charlen, bytelen);
3574 fclose(fo);
3578 if (IS_GFX(base->mode)) {
3579 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
3580 } else if (!needConversion) {
3581 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, winx, winy, s, bytelen);
3582 } else {
3583 if (bytelen > 0) {
3584 //k8: dunno why, but Xutf8DrawImageString() ignores ascii (at least for terminus)
3585 const char *pos = s;
3586 int xpos = winx;
3588 while (pos < s+bytelen) {
3589 const char *e;
3590 int clen;
3592 if ((unsigned char)(pos[0]) < 128) {
3593 for (e = pos+1; e < s+bytelen && (unsigned char)(*e) < 128; ++e) ;
3594 clen = e-pos;
3595 XmbDrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
3598 FILE *fo = fopen("zlog.log", "ab");
3599 fprintf(fo, " norm: clen=%d (%d); [", clen, (int)(e-pos));
3600 fwrite(pos, 1, e-pos, fo);
3601 fprintf(fo, "]\n");
3602 fclose(fo);
3605 } else {
3606 for (clen = 0, e = pos; e < s+bytelen && (unsigned char)(*e) >= 128; ++e) {
3607 if (((unsigned char)(e[0])&0xc0) == 0xc0) ++clen;
3609 Xutf8DrawImageString(xw.dpy, term->picbuf, fontset, dc.gc, xpos, winy, pos, e-pos);
3612 FILE *fo = fopen("zlog.log", "ab");
3613 fprintf(fo, " utf8: clen=%d (%d); [", clen, (int)(e-pos));
3614 fwrite(pos, 1, e-pos, fo);
3615 fprintf(fo, "]\n");
3616 fclose(fo);
3620 xpos += xw.cw*clen;
3621 pos = e;
3626 if (base->mode & ATTR_UNDERLINE) {
3627 XDrawLine(xw.dpy, term->picbuf, dc.gc, winx, winy+1, winx+width-1, winy+1);
3632 /* copy buffer pixmap to screen pixmap */
3633 static void xcopy (int x, int y, int cols, int rows) {
3634 int src_x = x*xw.cw, src_y = y*xw.ch, src_w = cols*xw.cw, src_h = rows*xw.ch;
3635 int dst_x = src_x, dst_y = src_y;
3637 if (opt_tabposition == 1) { dst_y += xw.tabheight; }
3638 XCopyArea(xw.dpy, term->picbuf, xw.win, dc.gc, src_x, src_y, src_w, src_h, dst_x, dst_y);
3642 static void xdrawcursor (void) {
3643 int sl;
3644 int scrx, scry;
3646 if (term == NULL) return;
3648 LIMIT(term->oldcx, 0, term->col-1);
3649 LIMIT(term->oldcy, 0, term->row-1);
3651 if (!term->cmdMode || term->oldcy != term->row-1) {
3652 scrx = term->oldcx;
3653 scry = term->oldcy+term->topline;
3654 if (scry < term->row &&
3655 (term->oldcy != term->c.y || term->oldcx != term->c.x || (term->c.state&CURSOR_HIDE) || !(xw.state & WIN_FOCUSED))) {
3656 /* remove the old cursor */
3657 sl = utf8size(term->line[term->oldcy][scrx].c);
3658 xdraws(term->line[term->oldcy][scrx].c, &term->line[term->oldcy][scrx], scrx, scry, 1, sl);
3659 //xclear(scrx, term->oldcy, scrx, term->oldcy);
3660 xcopy(scrx, scry, 1, 1);
3663 if (term->cmdMode && term->oldcy == term->row-1) return;
3664 /* draw the new one */
3665 if (!(term->c.state&CURSOR_HIDE)) {
3666 Glyph g;
3668 scrx = term->c.x;
3669 scry = term->c.y+term->topline;
3670 if (scry < term->row) {
3671 if (!(xw.state & WIN_FOCUSED)) {
3672 if (defaultCursorInactiveBG < 0) {
3673 XSetForeground(xw.dpy, dc.gc, dc.col[defaultCursorBG]);
3674 XDrawRectangle(xw.dpy, term->picbuf, dc.gc, scrx*xw.cw, scry*xw.ch, xw.cw-1, xw.ch-1);
3675 goto done;
3677 g.bg = defaultCursorInactiveBG;
3678 g.fg = defaultCursorInactiveFG;
3679 } else {
3680 g.fg = defaultCursorFG;
3681 g.bg = defaultCursorBG;
3683 memcpy(g.c, term->line[term->c.y][scrx].c, UTF_SIZ);
3684 g.state = 0;
3685 g.mode = 0;
3686 if (IS_SET(MODE_REVERSE)) g.mode |= ATTR_REVERSE;
3687 sl = utf8size(g.c);
3688 xdraws(g.c, &g, scrx, scry, 1, sl);
3689 term->oldcx = scrx;
3690 term->oldcy = term->c.y;
3692 done:
3693 xcopy(scrx, scry, 1, 1);
3698 static void xdrawTabBar (void) {
3699 if (xw.tabheight > 0 && updateTabBar) {
3700 static int tableft = 0;
3701 int tabstart;
3702 int tabw = xw.w/opt_tabcount;
3703 XFontSet fontset = dc.font[2].set;
3705 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabBG]);
3706 XFillRectangle(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, xw.tabheight);
3708 if (termidx < tableft) tableft = termidx;
3709 else if (termidx > tableft+opt_tabcount-1) tableft = termidx-opt_tabcount+1;
3710 if (tableft < 0) tableft = 0;
3712 tabstart = tableft;
3713 for (int f = tabstart; f < tabstart+opt_tabcount; ++f) {
3714 int x = (f-tabstart)*tabw;
3715 const char *title;
3716 char *tit;
3718 if (f >= term_count) {
3719 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabBG]);
3720 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, xw.w, xw.tabheight);
3722 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabFG]);
3723 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
3724 break;
3726 title = term_array[f]->title;
3727 if (!title[0]) title = opt_title;
3728 tit = SPrintf("[%d]%s", f, title);
3729 title = tit;
3731 XSetForeground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabBG : normalTabBG]);
3732 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x, 0, tabw, xw.tabheight);
3734 XSetBackground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabBG : normalTabBG]);
3735 XSetForeground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabFG : normalTabFG]);
3737 if (needConversion) {
3738 int xx = x+2;
3740 while (*title && xx < x+tabw) {
3741 const char *e = title;
3742 XRectangle r;
3744 memset(&r, 0, sizeof(r));
3746 if ((unsigned char)(*e) > 127) {
3747 while (*e && (unsigned char)(*e) > 127) ++e;
3748 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
3749 Xutf8TextExtents(fontset, title, e-title, &r, NULL);
3750 } else {
3751 while (*e && (unsigned char)(*e) <= 127) ++e;
3752 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, xx, dc.font[2].ascent+2, title, e-title);
3753 XmbTextExtents(fontset, title, e-title, &r, NULL);
3755 title = e;
3756 xx += r.width-r.x;
3759 XmbDrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
3760 } else {
3761 Xutf8DrawImageString(xw.dpy, xw.pictab, fontset, dc.gc, x+2, dc.font[2].ascent+2, title, strlen(title));
3763 free(tit);
3765 XSetForeground(xw.dpy, dc.gc, dc.col[f == termidx ? activeTabBG : normalTabBG]);
3766 XFillRectangle(xw.dpy, xw.pictab, dc.gc, x+tabw-2, 0, 2, xw.tabheight);
3768 if (f > tabstart) {
3769 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabFG]);
3770 XDrawLine(xw.dpy, xw.pictab, dc.gc, x/*+tabw-1*/, 0, x/*+tabw-1*/, xw.tabheight);
3774 XSetForeground(xw.dpy, dc.gc, dc.col[normalTabFG]);
3775 //XDrawRectangle(xw.dpy, xw.pictab, dc.gc, -1, 0, xw.w+1, xw.tabheight+1);
3776 if (opt_tabposition == 0) {
3777 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, 0, xw.w, 0);
3778 } else {
3779 XDrawLine(xw.dpy, xw.pictab, dc.gc, 0, xw.tabheight-1, xw.w, xw.tabheight-1);
3782 if (opt_tabposition == 0) {
3783 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, xw.h-xw.tabheight);
3784 } else {
3785 XCopyArea(xw.dpy, xw.pictab, xw.win, dc.gc, 0, 0, xw.w, xw.tabheight, 0, 0);
3787 updateTabBar = 0;
3791 static void drawcmdline (int scry) {
3792 Glyph base;
3793 int cpos = term->cmdofs, bc = 0, x, sx;
3795 base.mode = 0;
3796 base.fg = 255;
3797 base.bg = 21;
3798 base.state = 0;
3799 for (sx = x = 0; x < term->col && term->cmdline[cpos]; ++x) {
3800 int l = utf8size(term->cmdline+cpos);
3802 if (bc+l > DRAW_BUF_SIZ) {
3803 xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
3804 bc = 0;
3805 sx = x;
3807 memcpy(term->drawbuf+bc, term->cmdline+cpos, l);
3808 cpos += l;
3809 bc += l;
3811 if (bc > 0) xdraws(term->drawbuf, &base, sx, scry, x-sx, bc);
3813 if (x < term->col) {
3814 base.fg = 20;
3815 base.bg = 255;
3816 xdraws(" ", &base, x, scry, 1, 1);
3817 ++x;
3820 if (x < term->col) {
3821 base.fg = 255;
3822 base.bg = 21;
3823 memset(term->drawbuf, ' ', DRAW_BUF_SIZ);
3824 while (x < term->col) {
3825 sx = x;
3826 x += DRAW_BUF_SIZ;
3827 if (x > term->col) x = term->col;
3828 xdraws(term->drawbuf, &base, sx, scry, x-sx, x-sx);
3832 xcopy(0, scry, term->col, 1);
3836 static void drawline (int x1, int x2, int scry, int lineno) {
3837 int ic, ib, ox, sl;
3838 int stx, ex;
3839 Glyph base, new;
3841 //dlogf("drawline: x1=%d; x2=%d; scry=%d; row:%d; lineno=%d\n", x1, x2, scry, term->row, lineno);
3842 if (scry < 0 || scry >= term->row) return;
3844 //if (lineno == term->row-1 && term->cmdMode) { drawcmdline(scry); return; }
3845 if (scry == term->row-1 && term->cmdMode) { drawcmdline(scry); return; }
3847 if (lineno < 0 || lineno >= term->linecount) {
3848 xclear(0, scry, term->col-1, scry);
3849 xcopy(0, scry, term->col, 1);
3850 } else {
3851 if (lineno < term->row && term->topline == 0) {
3852 //if (term->topline != 0) term->dirty[lineno] = 2;
3853 if (!term->dirty[lineno]) return;
3854 // fix 'dirty' flag for line
3855 if (term->dirty[lineno]&0x02) {
3856 // mark full line as dirty
3857 for (int x = 0; x < term->col; ++x) term->line[lineno][x].state |= GLYPH_DIRTY;
3859 // correct 'dirty' flag
3860 term->dirty[lineno] = 0;
3861 if (x1 > 0) for (int x = 0; x < x1; ++x) if (term->line[lineno][x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
3862 if (!term->dirty[lineno] && x2 < term->col) for (int x = x2; x < term->col; ++x) if (term->line[lineno][x].state&GLYPH_DIRTY) { term->dirty[lineno] = 1; break; }
3864 // find dirty region
3865 for (stx = x1; stx < x2; ++stx) if (term->line[lineno][stx].state&GLYPH_DIRTY) break;
3866 for (ex = x2; ex > stx; --ex) if (term->line[lineno][ex-1].state&GLYPH_DIRTY) break;
3867 if (stx >= x2 || ex <= stx) return; // nothing to do
3868 //dlogf(" region: (%d,%d)\n", stx, ex);
3869 } else {
3870 //if (lineno < term->row) term->dirty[lineno] = 0;
3871 stx = 0;
3872 ex = term->col;
3875 base = term->line[lineno][stx];
3876 ic = ib = 0;
3877 ox = stx;
3878 //xclear(stx, scry, ex-1, scry); //k8: actually, we don't need this for good monospace fonts, so...
3879 for (int x = stx; x < ex; ++x) {
3880 new = term->line[lineno][x];
3881 term->line[lineno][x].state &= ~GLYPH_DIRTY; //!
3882 if (term->sel.bx != -1 && new.c[0]) {
3883 if ((lineno < term->row && selected(x, lineno)) || (lineno >= term->row && selected(x, 0-(lineno-term->row+1)))) new.mode ^= ATTR_REVERSE;
3885 if (ib > 0 && (ATTRCMP(base, new) || ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
3886 // flush draw buffer
3887 xdraws(term->drawbuf, &base, ox, scry, ic, ib);
3888 ic = ib = 0;
3890 if (ib == 0) { ox = x; base = new; }
3891 sl = utf8size(new.c);
3892 memcpy(term->drawbuf+ib, new.c, sl);
3893 ib += sl;
3894 ++ic;
3896 if (ib > 0) xdraws(term->drawbuf, &base, ox, scry, ic, ib);
3897 //xcopy(0, scry, term->col, 1);
3898 if (term->c.y == lineno && term->c.x >= stx && term->c.x < ex) xdrawcursor();
3899 xcopy(stx, scry, ex-stx, 1);
3904 static void drawregion (int x1, int y1, int x2, int y2, int forced) {
3905 if (!forced && (xw.state&WIN_VISIBLE) == 0) {
3906 //dlogf("invisible");
3907 term->lastDrawTime = 1;
3908 term->wantRedraw = 1;
3909 return;
3912 if (term->topline < term->row) {
3913 for (int y = y1; y < y2; ++y) drawline(x1, x2, y+term->topline, y);
3915 if (term->topline > 0) {
3916 int scry = MIN(term->topline, term->row), y = term->row;
3918 if (term->topline >= term->row) y += term->topline-term->row;
3919 while (--scry >= 0) {
3920 drawline(0, term->col, scry, y);
3921 ++y;
3924 xdrawcursor();
3925 xdrawTabBar();
3926 //XFlush(xw.dpy);
3927 term->lastDrawTime = mclock_ticks();
3928 term->wantRedraw = 0;
3929 lastDrawTime = mclock_ticks();
3933 static void draw (int forced) {
3934 if (term != NULL) {
3935 //dlogf("draw(%d)\n", forced);
3936 drawregion(0, 0, term->col, term->row, forced);
3941 static void expose (XEvent *ev) {
3942 XExposeEvent *e = &ev->xexpose;
3944 if (xw.state&WIN_REDRAW) {
3945 if (!e->count && term != NULL) {
3946 xw.state &= ~WIN_REDRAW;
3947 xcopy(0, 0, term->col, term->row);
3949 } else if (term != NULL) {
3950 //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)));
3951 xcopy(0, 0, term->col, term->row);
3953 xdrawTabBar();
3954 //XFlush(xw.dpy);
3958 static void visibility (XEvent *ev) {
3959 XVisibilityEvent *e = &ev->xvisibility;
3961 if (e->state == VisibilityFullyObscured) xw.state &= ~WIN_VISIBLE;
3962 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 */
3966 static void unmap (XEvent *ev) {
3967 xw.state &= ~WIN_VISIBLE;
3971 static void xseturgency (int add) {
3972 XWMHints *h = XGetWMHints(xw.dpy, xw.win);
3974 h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
3975 XSetWMHints(xw.dpy, xw.win, h);
3976 XFree(h);
3980 static void focus (XEvent *ev) {
3981 if (ev->type == FocusIn) {
3982 xw.state |= WIN_FOCUSED;
3983 xseturgency(0);
3984 tsendfocusevent(1);
3985 } else {
3986 xw.state &= ~WIN_FOCUSED;
3987 tsendfocusevent(0);
3989 //draw(1);
3990 xdrawcursor();
3991 xdrawTabBar();
3992 xcopy(0, 0, term->col, term->row);
3996 ////////////////////////////////////////////////////////////////////////////////
3997 // keyboard mapping
3998 static const char *kmap (KeySym k, uint state) {
3999 const char *res = NULL;
4001 state &= ~Mod2Mask; // numlock
4002 for (int f = 0; f < keymap_used; ++f) {
4003 uint mask = keymap[f].mask;
4005 if (keymap[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) {
4006 //fprintf(stderr, "kp: %d\n", IS_SET(MODE_APPKEYPAD));
4007 if (!IS_SET(MODE_APPKEYPAD)) {
4008 if (!keymap[f].kp) {
4009 //fprintf(stderr, "nokp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4010 return keymap[f].str; // non-keypad hit
4012 continue;
4014 //fprintf(stderr, "kp! %d %s\n", keymap[f].kp, keymap[f].str+1);
4015 if (keymap[f].kp) return keymap[f].str; // keypad hit
4016 res = keymap[f].str; // kp mode, but non-kp mapping found
4019 return res;
4023 static const char *kbind (KeySym k, uint state) {
4024 state &= ~Mod2Mask; // numlock
4025 for (int f = 0; f < keybinds_used; ++f) {
4026 uint mask = keybinds[f].mask;
4028 if (keybinds[f].key == k && ((mask == XK_NO_MOD && !state) || state == mask)) return keybinds[f].str;
4030 return NULL;
4034 static KeySym do_keytrans (KeySym ks) {
4035 for (int f = 0; f < keytrans_used; ++f) if (keytrans[f].src == ks) return keytrans[f].dst;
4036 return ks;
4040 static void kpress (XEvent *ev) {
4041 XKeyEvent *e = &ev->xkey;
4042 KeySym ksym = NoSymbol;
4043 const char *kstr;
4044 int len;
4045 Status status;
4046 char buf[32];
4048 if (term == NULL) return;
4050 if (!ptrBlanked && opt_ptrblank > 0) xblankPointer();
4052 //if (len < 1) len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
4053 if ((len = Xutf8LookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status)) > 0) buf[len] = 0;
4054 // leave only known mods
4055 e->state &= (Mod1Mask | Mod4Mask | ControlMask | ShiftMask);
4056 #ifdef DUMP_KEYSYMS
4058 const char *ksname = XKeysymToString(ksym);
4060 fprintf(stderr, "utf(%d):[%s] (%s) 0x%08x\n", len, len>=0?buf:"<shit>", ksname, (unsigned int)e->state);
4062 #endif
4063 if ((kstr = kbind(ksym, e->state)) != NULL) {
4064 // keybind found
4065 executeCommands(kstr);
4066 return;
4069 if (term->cmdMode) {
4070 switch (do_keytrans(ksym)) {
4071 case XK_Return:
4072 tcmdlinehide();
4073 executeCommands(term->cmdline);
4074 break;
4075 case XK_BackSpace:
4076 utf8choplast(term->cmdline);
4077 tcmdlinefixofs();
4078 break;
4079 case XK_Escape:
4080 tcmdlinehide();
4081 break;
4082 default:
4083 if (len > 0 && (unsigned char)buf[0] >= 32) tcmdput(buf, len);
4084 break;
4086 return;
4089 if ((kstr = kmap(do_keytrans(ksym), e->state)) != NULL) {
4090 if (kstr[0]) {
4091 tunshowhistory();
4092 ttywritestr(kstr);
4094 } else {
4095 int meta = (e->state&Mod1Mask);
4097 int shift = (e->state&ShiftMask);
4098 int ctrl = (e->state&ControlMask);
4100 switch (ksym) {
4101 case XK_Return:
4102 tunshowhistory();
4103 if (meta) {
4104 ttywritestr("\x1b\x0a");
4105 } else {
4106 if (IS_SET(MODE_CRLF)) ttywritestr("\r\n"); else ttywritestr("\r");
4108 break;
4109 default:
4110 if (len > 0) {
4111 tunshowhistory();
4112 if (meta && len == 1) ttywritestr("\x1b");
4113 ttywrite(buf, len);
4115 break;
4121 ////////////////////////////////////////////////////////////////////////////////
4122 // xembed?
4123 static void cmessage (XEvent *e) {
4124 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
4125 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
4126 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
4127 xw.state |= WIN_FOCUSED;
4128 xseturgency(0);
4129 tsendfocusevent(1);
4130 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
4131 xw.state &= ~WIN_FOCUSED;
4132 tsendfocusevent(0);
4134 xdrawcursor();
4135 xdrawTabBar();
4136 xcopy(0, 0, term->col, term->row);
4141 ////////////////////////////////////////////////////////////////////////////////
4142 static void resize (XEvent *e) {
4143 int col, row;
4144 Term *ot = term;
4146 //if (e->xconfigure.width == 65535 || e->xconfigure.width == -1) e->xconfigure.width = xw.w;
4147 if (e->xconfigure.height == 65535 || e->xconfigure.height == -1) e->xconfigure.height = xw.h;
4148 //if ((short int)e->xconfigure.height < xw.ch) return;
4150 if (e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) return;
4151 xw.w = e->xconfigure.width;
4152 xw.h = e->xconfigure.height;
4153 col = xw.w/xw.cw;
4154 row = (xw.h-xw.tabheight)/xw.ch;
4155 //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);
4156 if (col == term->col && row == term->row) return;
4157 for (int f = 0; f < term_count; ++f) {
4158 term = term_array[f];
4159 if (tresize(col, row) && ot == term) draw(1);
4160 ttyresize();
4161 xresize(col, row);
4163 term = ot;
4164 XFreePixmap(xw.dpy, xw.pictab);
4165 xw.pictab = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.tabheight>0?xw.tabheight:1, XDefaultDepth(xw.dpy, xw.scr));
4166 updateTabBar = 1;
4170 static inline int last_draw_too_old (void) {
4171 int tt = mclock_ticks();
4173 if (term != NULL && !term->dead && term->wantRedraw && tt-term->lastDrawTime >= opt_drawtimeout) return 1;
4174 return (tt-lastDrawTime >= 500);
4178 ////////////////////////////////////////////////////////////////////////////////
4179 // main loop
4180 static void (*handler[LASTEvent])(XEvent *) = {
4181 [KeyPress] = kpress,
4182 [ClientMessage] = cmessage,
4183 [ConfigureNotify] = resize,
4184 [VisibilityNotify] = visibility,
4185 [UnmapNotify] = unmap,
4186 [Expose] = expose,
4187 [FocusIn] = focus,
4188 [FocusOut] = focus,
4189 [MotionNotify] = bmotion,
4190 [ButtonPress] = bpress,
4191 [ButtonRelease] = brelease,
4192 [SelectionNotify] = selnotify,
4193 [SelectionRequest] = selrequest,
4194 [SelectionClear] = selclear,
4198 static void run (void) {
4199 //int stuff_to_print = 0;
4200 int xfd = XConnectionNumber(xw.dpy);
4202 ptrLastMove = mclock_ticks();
4203 while (term_count > 0) {
4204 XEvent ev;
4205 fd_set rfd, wfd;
4206 struct timeval timeout;
4207 int maxfd = xfd;
4208 Term *ot;
4210 FD_ZERO(&rfd);
4211 FD_ZERO(&wfd);
4212 FD_SET(xfd, &rfd);
4213 //FD_SET(term->cmdfd, &rfd);
4214 // have something to write?
4215 for (int f = 0; f < term_count; ++f) {
4216 Term *t = term_array[f];
4218 if (!t->dead && term->cmdfd >= 0 && t->pid != 0) {
4219 if (t->cmdfd > maxfd) maxfd = t->cmdfd;
4220 FD_SET(t->cmdfd, &rfd);
4221 if (t->wrbufpos < t->wrbufused) FD_SET(t->cmdfd, &wfd);
4225 timeout.tv_sec = 0;
4226 timeout.tv_usec = (opt_drawtimeout+2)*1000;
4227 if (select(maxfd+1, &rfd, &wfd, NULL, &timeout) < 0) {
4228 if (errno == EINTR) continue;
4229 die("select failed: %s", SERRNO);
4232 ot = term;
4233 for (int f = 0; f < term_count; ++f) {
4234 Term *t = term_array[f];
4236 if (!t->dead && term->cmdfd >= 0 && term->pid != 0) {
4237 term = t;
4238 if (FD_ISSET(t->cmdfd, &wfd)) ttyflushwrbuf();
4239 if (FD_ISSET(t->cmdfd, &rfd)) ttyread(); //t->wantRedraw = 1;
4240 term = ot;
4244 termcleanup();
4245 if (term_count == 0) exit(exitcode);
4247 if (updateTabBar || last_draw_too_old()) {
4248 draw(0);
4251 if (XPending(xw.dpy)) {
4252 while (XPending(xw.dpy)) {
4253 XNextEvent(xw.dpy, &ev);
4254 switch (ev.type) {
4255 //case VisibilityNotify:
4256 //case UnmapNotify:
4257 //case FocusIn:
4258 //case FocusOut:
4259 case MotionNotify:
4260 case ButtonPress:
4261 case ButtonRelease:
4262 xunblankPointer();
4263 ptrLastMove = mclock_ticks();
4264 break;
4265 default: ;
4267 if (XFilterEvent(&ev, xw.win)) continue;
4268 if (handler[ev.type]) (handler[ev.type])(&ev);
4272 if (!ptrBlanked && opt_ptrblank > 0 && mclock_ticks()-ptrLastMove >= opt_ptrblank) {
4273 xblankPointer();
4279 ////////////////////////////////////////////////////////////////////////////////
4280 typedef const char * (*IniHandlerFn) (const char *optname, const char *fmt, char *argstr, void *udata);
4283 typedef struct {
4284 const char *name;
4285 const char *fmt;
4286 void *udata;
4287 IniHandlerFn fn;
4288 } IniCommand;
4290 static const char *inifnGenericOneArg (const char *optname, const char *fmt, char *argstr, void *udata) {
4291 return iniParseArguments(argstr, fmt, udata);
4295 static const char *inifnGenericOneStr (const char *optname, const char *fmt, char *argstr, void *udata) {
4296 char *s = NULL;
4297 const char *err = iniParseArguments(argstr, fmt, &s);
4299 if (err != NULL) return err;
4300 if ((s = strdup(s)) == NULL) return "out of memory";
4301 if (udata) {
4302 char **ustr = (char **)udata;
4304 if (*ustr) free(*ustr);
4305 *ustr = s;
4307 return NULL;
4311 static const IniCommand iniCommands[] = {
4312 {"term", "s!-", &opt_term, inifnGenericOneStr},
4313 {"class", "s!-", &opt_class, inifnGenericOneStr},
4314 {"title", "s!-", &opt_title, inifnGenericOneStr},
4315 {"fontnorm", "s!-", &opt_fontnorm, inifnGenericOneStr},
4316 {"fontbold", "s!-", &opt_fontbold, inifnGenericOneStr},
4317 {"fonttab", "s!-", &opt_fonttab, inifnGenericOneStr},
4318 {"shell", "s!-", &opt_shell, inifnGenericOneStr},
4319 {"doubleclick_timeout", "i{0,10000}", &opt_doubleclick_timeout, inifnGenericOneArg},
4320 {"tripleclick_timeout", "i{0,10000}", &opt_tripleclick_timeout, inifnGenericOneArg},
4321 {"tabsize", "i{1,256}", &opt_tabsize, inifnGenericOneArg},
4322 {"defaultfg", "i{0,511}", &defaultFG, inifnGenericOneArg},
4323 {"defaultbg", "i{0,511}", &defaultBG, inifnGenericOneArg},
4324 {"defaultcursorfg", "i{0,511}", &defaultCursorFG, inifnGenericOneArg},
4325 {"defaultcursorbg", "i{0,511}", &defaultCursorBG, inifnGenericOneArg},
4326 {"defaultinactivecursorfg", "i{0,511}", &defaultCursorInactiveFG, inifnGenericOneArg},
4327 {"defaultinactivecursorbg", "i{-1,511}", &defaultCursorInactiveBG, inifnGenericOneArg},
4328 {"defaultboldfg", "i{-1,511}", &defaultBoldFG, inifnGenericOneArg},
4329 {"defaultunderlinefg", "i{-1,511}", &defaultUnderlineFG, inifnGenericOneArg},
4330 {"normaltabfg", "i{0,511}", &normalTabFG, inifnGenericOneArg},
4331 {"normaltabbg", "i{0,511}", &normalTabBG, inifnGenericOneArg},
4332 {"activetabfg", "i{0,511}", &activeTabFG, inifnGenericOneArg},
4333 {"activetabbg", "i{0,511}", &activeTabBG, inifnGenericOneArg},
4334 {"maxhistory", "i{0,65535}", &opt_maxhistory, inifnGenericOneArg},
4335 {"ptrblank", "i{0,65535}", &opt_ptrblank, inifnGenericOneArg},
4336 {"tabcount", "i{1,128}", &opt_tabcount, inifnGenericOneArg},
4337 {"tabposition", "i{0,1}", &opt_tabposition, inifnGenericOneArg},
4338 {"draw_timeout", "i{5,30000}", &opt_drawtimeout, inifnGenericOneArg},
4339 {"audiblebell", "b", &opt_audiblebell, inifnGenericOneArg},
4340 {NULL, NULL, NULL, NULL}
4344 #define MISC_CMD_NONE ((const char *)-1)
4347 // NULL: command processed; MISC_CMD_NONE: unknown command; !NULL: error
4348 static const char *processMiscCmds (const char *optname, char *argstr) {
4349 const char *err = NULL;
4351 if (strcasecmp(optname, "unimap") == 0) {
4352 int uni, ch;
4353 char *alt = NULL;
4355 //unimap 0x2592 0x61 alt
4356 if ((err = iniParseArguments(argstr, "i{0,65535}i{0,255}|s!-", &uni, &ch, &alt)) != NULL) return err;
4357 if (alt != NULL && strcasecmp(alt, "alt") != 0) return "invalid unimap";
4358 if (unimap == NULL) {
4359 if ((unimap = calloc(65536, sizeof(unimap[0]))) == NULL) return "out of memory";
4361 if (alt != NULL && ch == 0) alt = NULL;
4362 if (alt != NULL && ch < 96) return "invalid unimap";
4363 unimap[uni] = ch;
4364 if (alt != NULL) unimap[uni] |= 0x8000;
4365 return NULL;
4368 if (strcasecmp(optname, "keytrans_reset") == 0) {
4369 if ((err = iniParseArguments(argstr, "")) != NULL) return err;
4370 keytrans_reset();
4371 return NULL;
4373 if (strcasecmp(optname, "keytrans") == 0) {
4374 char *src = NULL, *dst = NULL;
4376 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
4377 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
4378 keytrans_reset();
4379 return NULL;
4382 if ((err = iniParseArguments(argstr, "s!-s!-", &src, &dst)) != NULL) return err;
4383 keytrans_add(src, dst);
4384 return NULL;
4387 if (strcasecmp(optname, "keybind_reset") == 0) {
4388 if ((err = iniParseArguments(argstr, "")) != NULL) return err;
4389 keybinds_reset();
4390 return NULL;
4392 if (strcasecmp(optname, "keybind") == 0) {
4393 char *key = NULL, *act = NULL;
4394 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
4395 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
4396 keybinds_reset();
4397 return NULL;
4400 if ((err = iniParseArguments(argstr, "s!-R!", &key, &act)) != NULL) return err;
4401 keybind_add(key, act);
4402 return NULL;
4405 if (strcasecmp(optname, "keymap_reset") == 0) {
4406 if ((err = iniParseArguments(argstr, "")) != NULL) return err;
4407 keymap_reset();
4408 return NULL;
4410 if (strcasecmp(optname, "keymap") == 0) {
4411 char *key = NULL, *str = NULL;
4413 if (iniParseArguments(argstr, "R!-", &argstr) == NULL) {
4414 if (strcasecmp(argstr, "reset") == 0 || strcasecmp(argstr, "clear") == 0) {
4415 keymap_reset();
4416 return NULL;
4419 if ((err = iniParseArguments(argstr, "s!-s!-", &key, &str)) != NULL) return err;
4420 keymap_add(key, str);
4421 return NULL;
4424 return MISC_CMD_NONE;
4428 #define INI_LINE_SIZE (32768)
4430 // <0: file not found
4431 // >0: file loading error
4432 // 0: ok
4433 static int loadConfig (const char *fname) {
4434 int inifelse = 0; // 0: not; 1: doing true; 2: doing false; -1: waiting else/endif; -2: waiting endif
4435 FILE *fi = fopen(fname, "r");
4436 const char *err = NULL;
4437 char *line;
4438 int lineno = 0;
4440 if (fi == NULL) return -1;
4441 if ((line = malloc(INI_LINE_SIZE)) == NULL) { err = "out of memory"; goto quit; }
4443 while (fgets(line, INI_LINE_SIZE-1, fi) != NULL) {
4444 char *optname, *argstr;
4445 int goodoption = 0;
4447 ++lineno;
4448 line[INI_LINE_SIZE-1] = 0;
4449 // get option name
4450 for (optname = line; *optname && isspace(*optname); ++optname) ;
4451 if (!optname[0] || optname[0] == '#') continue; // comment
4452 if (!isalnum(optname[0])) { err = "invalid option name"; goto quit; }
4453 argstr = optname;
4454 while (*argstr) {
4455 if (!argstr[0] || isspace(argstr[0])) break;
4456 if (!isalnum(argstr[0]) && argstr[0] != '_' && argstr[0] != '.') { err = "invalid option name"; goto quit; }
4457 *argstr = tolower(*argstr);
4458 ++argstr;
4460 if (*argstr) *argstr++ = 0;
4462 if (strcasecmp(optname, "ifterm") == 0) {
4463 char *val;
4465 if (inifelse != 0) { err = "nested ifs are not allowed"; goto quit; }
4466 inifelse = -1;
4467 if ((err = iniParseArguments(argstr, "s", &val)) != NULL) goto quit;
4468 if (strcasecmp(opt_term, val) == 0) inifelse = 1;
4469 continue;
4471 if (strcasecmp(optname, "else") == 0) {
4472 switch (inifelse) {
4473 case -1: inifelse = 2; break;
4474 case 2: case -2: case 0: err = "else without if"; goto quit;
4475 case 1: inifelse = -2; break;
4477 continue;
4479 if (strcasecmp(optname, "endif") == 0) {
4480 switch (inifelse) {
4481 case -1: case -2: case 1: case 2: inifelse = 0; break;
4482 case 0: err = "endif without if"; goto quit;
4484 continue;
4487 if (inifelse < 0) {
4488 //trimstr(argstr);
4489 //fprintf(stderr, "skip: [%s]\n", argstr);
4490 continue;
4492 if (opt_term_locked && strcasecmp(optname, "term") == 0) continue; // termname given in command line
4493 // ok, we have option name in `optname` and arguments in `argstr`
4494 if (strncmp(optname, "color.", 6) == 0) {
4495 int n = 0;
4496 char *s = NULL;
4498 optname += 6;
4499 if (!optname[0]) { err = "invalid color option"; goto quit; }
4500 while (*optname) {
4501 if (!isdigit(*optname)) { err = "invalid color option"; goto quit; }
4502 n = (n*10)+(optname[0]-'0');
4503 ++optname;
4505 if (n < 0 || n > 511) { err = "invalid color index"; goto quit; }
4507 if ((err = iniParseArguments(argstr, "s!-", &s)) != NULL) goto quit;
4508 if ((s = strdup(s)) == NULL) { err = "out of memory"; goto quit; }
4509 if (opt_colornames[n] != NULL) free(opt_colornames[n]);
4510 opt_colornames[n] = s;
4511 continue;
4514 if ((err = processMiscCmds(optname, argstr)) != MISC_CMD_NONE) {
4515 if (err != NULL) goto quit;
4516 continue;
4517 } else {
4518 err = NULL;
4521 for (int f = 0; iniCommands[f].name != NULL; ++f) {
4522 if (strcmp(iniCommands[f].name, optname) == 0) {
4523 if ((err = iniCommands[f].fn(optname, iniCommands[f].fmt, argstr, iniCommands[f].udata)) != NULL) goto quit;
4524 goodoption = 1;
4525 break;
4528 if (!goodoption) {
4529 fprintf(stderr, "ini error at line %d: unknown option '%s'!\n", lineno, optname);
4532 quit:
4533 if (line != NULL) free(line);
4534 fclose(fi);
4535 if (err == NULL && inifelse != 0) err = "if without endif";
4536 if (err != NULL) die("ini error at line %d: %s", lineno, err);
4537 return 0;
4541 static void initDefaultOptions (void) {
4542 opt_title = strdup("sterm");
4543 opt_class = strdup("sterm");
4544 opt_term = strdup(TNAME);
4545 opt_fontnorm = strdup(FONT);
4546 opt_fontbold = strdup(FONTBOLD);
4547 opt_fonttab = strdup(FONTTAB);
4548 opt_shell = strdup(SHELL);
4550 memset(opt_colornames, 0, sizeof(opt_colornames));
4551 for (int f = 0; f < LEN(defcolornames); ++f) opt_colornames[f] = strdup(defcolornames[f]);
4552 for (int f = 0; f < LEN(defextcolornames); ++f) opt_colornames[f+256] = strdup(defextcolornames[f]);
4554 keytrans_add("KP_Home", "Home");
4555 keytrans_add("KP_Left", "Left");
4556 keytrans_add("KP_Up", "Up");
4557 keytrans_add("KP_Right", "Right");
4558 keytrans_add("KP_Down", "Down");
4559 keytrans_add("KP_Prior", "Prior");
4560 keytrans_add("KP_Next", "Next");
4561 keytrans_add("KP_End", "End");
4562 keytrans_add("KP_Begin", "Begin");
4563 keytrans_add("KP_Insert", "Insert");
4564 keytrans_add("KP_Delete", "Delete");
4566 keybind_add("shift+Insert", "PastePrimary");
4567 keybind_add("alt+Insert", "PasteSecondary");
4569 keymap_add("BackSpace", "\177");
4570 keymap_add("Insert", "\x1b[2~");
4571 keymap_add("Delete", "\x1b[3~");
4572 keymap_add("Home", "\x1b[1~");
4573 keymap_add("End", "\x1b[4~");
4574 keymap_add("Prior", "\x1b[5~");
4575 keymap_add("Next", "\x1b[6~");
4576 keymap_add("F1", "\x1bOP");
4577 keymap_add("F2", "\x1bOQ");
4578 keymap_add("F3", "\x1bOR");
4579 keymap_add("F4", "\x1bOS");
4580 keymap_add("F5", "\x1b[15~");
4581 keymap_add("F6", "\x1b[17~");
4582 keymap_add("F7", "\x1b[18~");
4583 keymap_add("F8", "\x1b[19~");
4584 keymap_add("F9", "\x1b[20~");
4585 keymap_add("F10", "\x1b[21~");
4586 keymap_add("Up", "\x1bOA");
4587 keymap_add("Down", "\x1bOB");
4588 keymap_add("Right", "\x1bOC");
4589 keymap_add("Left", "\x1bOD");
4590 keymap_add("kpad+Up", "\x1bOA");
4591 keymap_add("kpad+Down", "\x1bOB");
4592 keymap_add("kpad+Right", "\x1bOC");
4593 keymap_add("kpad+Left", "\x1bOD");
4597 ////////////////////////////////////////////////////////////////////////////////
4598 static Term *oldTerm;
4599 static int oldTermIdx;
4600 static Term *newTerm;
4601 static int newTermIdx;
4602 static int newTermSwitch;
4605 typedef void (*CmdHandlerFn) (const char *cmdname, char *argstr);
4607 typedef struct {
4608 const char *name;
4609 CmdHandlerFn fn;
4610 } Command;
4613 static void cmdPastePrimary (const char *cmdname, char *argstr) {
4614 selpaste(XA_PRIMARY);
4618 static void cmdPasteSecondary (const char *cmdname, char *argstr) {
4619 selpaste(XA_SECONDARY);
4623 static void cmdPasteClipboard (const char *cmdname, char *argstr) {
4624 selpaste(XA_CLIPBOARD);
4628 static void cmdExec (const char *cmdname, char *argstr) {
4629 if (term->execcmd != NULL) free(term->execcmd);
4630 if (argstr[0]) {
4631 term->execcmd = strdup(argstr);
4632 } else {
4633 term->execcmd = NULL;
4638 static int parseTabArgs (char *argstr, int *noswitch, int nowrap, int idx) {
4639 for (;;) {
4640 char *arg;
4642 while (*argstr && isspace(*argstr)) ++argstr;
4643 if (!argstr[0]) break;
4644 if (iniParseArguments(argstr, "s-R-", &arg, &argstr) != NULL) break;
4646 if (strcasecmp(arg, "noswitch") == 0) *noswitch = 1;
4647 else if (strcasecmp(arg, "switch") == 0) *noswitch = 0;
4648 else if (strcasecmp(arg, "nowrap") == 0) nowrap = 1;
4649 else if (strcasecmp(arg, "wrap") == 0) nowrap = 0;
4650 else if (strcasecmp(arg, "first") == 0) idx = 0;
4651 else if (strcasecmp(arg, "last") == 0) idx = term_count-1;
4652 else if (strcasecmp(arg, "prev") == 0) idx = -1;
4653 else if (strcasecmp(arg, "next") == 0) idx = -2;
4654 else {
4655 long int n = -1;
4656 char *eptr;
4658 n = strtol(arg, &eptr, 0);
4659 if (!eptr[0] && n >= 0 && n < term_count) idx = n;
4662 switch (idx) {
4663 case -1: // prev
4664 if ((idx = termidx-1) < 0) idx = nowrap ? 0 : term_count-1;
4665 break;
4666 case -2: // next
4667 if ((idx = termidx+1) >= term_count) idx = nowrap ? term_count-1 : 0;
4668 break;
4670 return idx;
4674 static void flushNewTerm (void) {
4675 if (newTerm != NULL) {
4676 if (newTermSwitch && term != NULL) term->lastActiveTime = mclock_ticks();
4677 term = newTerm;
4678 termidx = newTermIdx;
4679 tinitialize(term_array[0]->col, term_array[0]->row);
4681 term->picbufh = term->row*xw.ch;
4682 term->picbufw = term->col*xw.cw;
4683 term->picbuf = XCreatePixmap(xw.dpy, xw.win, term->picbufw, term->picbufh, XDefaultDepth(xw.dpy, xw.scr));
4684 XFillRectangle(xw.dpy, term->picbuf, dc.gc, 0, 0, term->picbufw, term->picbufh);
4686 if (ttynew(term) != 0) {
4687 term = oldTerm;
4688 termidx = oldTermIdx;
4689 termfree(newTermIdx);
4690 } else {
4691 selinit();
4692 ttyresize();
4693 if (newTermSwitch) {
4694 term = NULL;
4695 termidx = 0;
4696 switchToTerm(newTermIdx, 1);
4697 oldTerm = term;
4698 oldTermIdx = termidx;
4699 } else {
4700 term = oldTerm;
4701 termidx = oldTermIdx;
4704 newTerm = NULL;
4709 static void cmdNewTab (const char *cmdname, char *argstr) {
4710 int noswitch = 0, idx;
4712 if (opt_disabletabs) return;
4713 flushNewTerm();
4714 if ((newTerm = termalloc()) == NULL) return;
4715 /*idx =*/ parseTabArgs(argstr, &noswitch, 0, termidx);
4716 idx = term_count-1;
4717 if (!noswitch) {
4718 if (term != NULL) term->lastActiveTime = mclock_ticks();
4719 oldTermIdx = idx;
4721 newTermIdx = termidx = idx;
4722 term = newTerm;
4723 newTermSwitch = !noswitch;
4727 static void cmdCloseTab (const char *cmdname, char *argstr) {
4728 flushNewTerm();
4729 if (!term->dead) kill(term->pid, SIGTERM);
4733 static void cmdKillTab (const char *cmdname, char *argstr) {
4734 flushNewTerm();
4735 if (!term->dead) kill(term->pid, SIGKILL);
4739 static void cmdSwitchToTab (const char *cmdname, char *argstr) {
4740 int noswitch = 0, idx;
4742 flushNewTerm();
4743 idx = parseTabArgs(argstr, &noswitch, 0, -666);
4744 if (idx >= 0) {
4745 switchToTerm(idx, 1);
4746 oldTerm = term;
4747 oldTermIdx = termidx;
4752 static void cmdMoveTabTo (const char *cmdname, char *argstr) {
4753 int noswitch = 0, idx;
4755 flushNewTerm();
4756 idx = parseTabArgs(argstr, &noswitch, 0, termidx);
4757 if (idx != termidx && idx >= 0 && idx < term_count) {
4758 Term *t = term_array[termidx];
4760 // remove current term
4761 for (int f = termidx+1; f < term_count; ++f) term_array[f-1] = term_array[f];
4762 // insert term
4763 for (int f = term_count-2; f >= idx; --f) term_array[f+1] = term_array[f];
4764 term_array[idx] = t;
4765 termidx = idx;
4766 oldTerm = t;
4767 oldTermIdx = idx;
4768 updateTabBar = 1;
4773 static void cmdDefaultFG (const char *cmdname, char *argstr) {
4774 char *s = NULL;
4775 int c;
4777 if (iniParseArguments(argstr, "i{0,511}|s-", &c, &s) == NULL) {
4778 if (s != NULL && tolower(s[0]) == 'g') defaultFG = c; else term->deffg = c;
4783 static void cmdDefaultBG (const char *cmdname, char *argstr) {
4784 char *s = NULL;
4785 int c;
4787 if (iniParseArguments(argstr, "i{0,511}|s-", &c) == NULL) {
4788 if (s != NULL && tolower(s[0]) == 'g') defaultBG = c; else term->defbg = c;
4793 static void scrollHistory (int delta) {
4794 if (term->maxhistory < 1) return; // no history
4795 term->topline += delta;
4796 if (term->topline > term->maxhistory-term->row) term->topline = term->maxhistory-term->row;
4797 if (term->topline < 0) term->topline = 0;
4798 tfulldirt();
4799 draw(1);
4803 static void cmdScrollHistoryLineUp (const char *cmdname, char *argstr) {
4804 scrollHistory(1);
4808 static void cmdScrollHistoryPageUp (const char *cmdname, char *argstr) {
4809 scrollHistory(term->row);
4813 static void cmdScrollHistoryLineDown (const char *cmdname, char *argstr) {
4814 scrollHistory(-1);
4818 static void cmdScrollHistoryPageDown (const char *cmdname, char *argstr) {
4819 scrollHistory(-term->row);
4823 static void cmdScrollHistoryTop (const char *cmdname, char *argstr) {
4824 scrollHistory(term->linecount);
4828 static void cmdScrollHistoryBottom (const char *cmdname, char *argstr) {
4829 scrollHistory(-term->linecount);
4833 static void cmdUTF8Locale (const char *cmdname, char *argstr) {
4834 int b;
4836 if (iniParseArguments(argstr, "b", &b) == NULL) {
4837 if (!needConversion) b = 1;
4838 if (term != NULL) term->needConv = !b;
4839 //fprintf(stderr, "needConv: %d (%d)\n", term->needConv, needConversion);
4844 static void cmdAudibleBell (const char *cmdname, char *argstr) {
4845 int b = -1;
4846 int toggle = 0, *iptr = &term->audiblebell;
4848 while (argstr[0]) {
4849 char *s = NULL;
4851 if (iniParseArguments(argstr, "R-", &argstr) != NULL) break;
4852 if (!argstr[0]) break;
4854 if (iniParseArguments(argstr, "b", &b) == NULL) continue;
4856 if (iniParseArguments(argstr, "s-R-", &s, &argstr) != NULL) break;
4858 if (strcasecmp(s, "toggle") == 0) toggle = 1;
4859 else if (tolower(s[0]) == 'g') iptr = &opt_audiblebell;
4862 if (toggle) *iptr = !(*iptr);
4863 else if (b != -1) *iptr = b;
4867 static void cmdCommandMode (const char *cmdname, char *argstr) {
4868 if (term == NULL) return;
4869 if (!term->cmdMode) tcmdlineinit();
4873 static const Command commandList[] = {
4874 {"PastePrimary", cmdPastePrimary},
4875 {"PasteSecondary", cmdPasteSecondary},
4876 {"PasteClipboard", cmdPasteClipboard},
4877 {"exec", cmdExec},
4878 {"NewTab", cmdNewTab}, // 'noswitch' 'next' 'prev' 'first' 'last'
4879 {"CloseTab", cmdCloseTab},
4880 {"KillTab", cmdKillTab},
4881 {"SwitchToTab", cmdSwitchToTab}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
4882 {"MoveTabTo", cmdMoveTabTo}, // 'next' 'prev' 'first' 'last' 'nowrap' 'wrap'
4883 {"defaultfg", cmdDefaultFG},
4884 {"defaultbg", cmdDefaultBG},
4885 {"ScrollHistoryLineUp", cmdScrollHistoryLineUp},
4886 {"ScrollHistoryPageUp", cmdScrollHistoryPageUp},
4887 {"ScrollHistoryLineDown", cmdScrollHistoryLineDown},
4888 {"ScrollHistoryPageDown", cmdScrollHistoryPageDown},
4889 {"ScrollHistoryTop", cmdScrollHistoryTop},
4890 {"ScrollHistoryBottom", cmdScrollHistoryBottom},
4891 {"UTF8Locale", cmdUTF8Locale}, // 'on', 'off'
4892 {"AudibleBell", cmdAudibleBell},
4893 {"CommandMode", cmdCommandMode},
4895 {"term", cmdTermName},
4896 {"title", cmdWinTitle},
4897 {"tabsize", cmdTabSize},
4898 {"defaultcursorfg", cmdDefaultCursorFG},
4899 {"defaultcursorbg", cmdDefaultCursorBG},
4900 {"defaultinactivecursorfg", cmdDefaultInactiveCursorFG},
4901 {"defaultinactivecursorbg", cmdDefaultInactiveCursorBG},
4902 {"defaultboldfg", cmdDefaultBoldFG},
4903 {"defaultunderlinefg", cmdDefaultUnderlineFG},
4905 {NULL, NULL}
4909 // !0: NewTab command
4910 static int executeCommand (const char *str, int slen) {
4911 const char *e;
4912 char *cmdname;
4913 int cmdfound = 0;
4915 if (str == NULL) return 0;
4916 if (slen < 0) slen = strlen(str);
4918 for (int f = 0; f < slen; ++f) if (!str[f]) { slen = f; break; }
4920 while (slen > 0 && isspace(*str)) { ++str; --slen; }
4921 if (slen < 1 || !str[0]) return 0;
4923 for (e = str; slen > 0 && !isspace(*e); ++e, --slen) ;
4925 if (e-str > 127) return 0;
4926 cmdname = alloca(e-str+8);
4927 if (cmdname == NULL) return 0;
4928 memcpy(cmdname, str, e-str);
4929 cmdname[e-str] = 0;
4930 if (opt_disabletabs && strcasecmp(cmdname, "NewTab") == 0) return 1;
4932 while (slen > 0 && isspace(*e)) { ++e; --slen; }
4933 //FIXME: ugly copypaste!
4935 for (int f = 0; commandList[f].name != NULL; ++f) {
4936 if (strcasecmp(commandList[f].name, cmdname) == 0) {
4937 char *left = calloc(slen+2, 1);
4939 if (left != NULL) {
4940 if (slen > 0) memcpy(left, e, slen);
4941 //fprintf(stderr, "command: [%s]; args: [%s]\n", cmdname, left);
4942 commandList[f].fn(cmdname, left);
4943 free(left);
4945 cmdfound = 1;
4946 break;
4950 if (!cmdfound) {
4951 char *left = calloc(slen+2, 1);
4953 if (left != NULL) {
4954 if (slen > 0) memcpy(left, e, slen);
4955 processMiscCmds(cmdname, left);
4956 free(left);
4960 return 0;
4964 static void executeCommands (const char *str) {
4965 oldTerm = term;
4966 oldTermIdx = termidx;
4967 newTerm = NULL;
4968 newTermSwitch = 0;
4969 if (str == NULL) return;
4970 while (*str) {
4971 const char *ce;
4972 char qch;
4974 while (*str && isspace(*str)) ++str;
4975 if (!*str) break;
4976 if (*str == ';') { ++str; continue; }
4978 ce = str;
4979 qch = ' ';
4980 while (*ce) {
4981 if (*ce == ';' && qch == ' ') break;
4982 if (qch != ' ' && *ce == qch) { qch = ' '; ++ce; continue; }
4983 if (*ce == '"' || *ce == '\'') {
4984 if (qch == ' ') qch = *ce;
4985 ++ce;
4986 continue;
4988 if (*ce++ == '\\' && *ce) ++ce;
4991 if (executeCommand(str, ce-str)) break;
4992 if (*ce) str = ce+1; else break;
4994 flushNewTerm();
4995 switchToTerm(oldTermIdx, 1);
4999 ////////////////////////////////////////////////////////////////////////////////
5000 int main (int argc, char *argv[]) {
5001 char *configfile = NULL;
5003 //dbgLogInit();
5005 for (int f = 1; f < argc; f++) {
5006 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
5007 if (strcmp(argv[f], "-into") == 0) { ++f; continue; }
5008 if (strcmp(argv[f], "-embed") == 0) { ++f; continue; }
5009 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
5010 case 'e': f = argc+1; break;
5011 case 't':
5012 case 'c':
5013 case 'w':
5014 case 'b':
5015 ++f;
5016 break;
5017 case 'T':
5018 if (++f < argc) {
5019 free(opt_term);
5020 opt_term = strdup(argv[f]);
5021 opt_term_locked = 1;
5023 break;
5024 case 'C':
5025 if (++f < argc) {
5026 if (configfile != NULL) free(configfile);
5027 configfile = strdup(argv[f]);
5029 break;
5030 case 'l':
5031 if (++f < argc) cliLocale = argv[f];
5032 break;
5033 case 'S': // single-tab mode
5034 opt_disabletabs = 1;
5035 break;
5036 case 'v':
5037 case 'h':
5038 default:
5039 fprintf(stderr, "%s", USAGE);
5040 exit(EXIT_FAILURE);
5044 initDefaultOptions();
5045 if (configfile == NULL) {
5046 const char *home = getenv("HOME");
5048 if (home != NULL) {
5049 configfile = SPrintf("%s/.sterm.rc", home);
5050 if (loadConfig(configfile) == 0) goto cfgdone;
5051 free(configfile); configfile = NULL;
5053 configfile = SPrintf("%s/.config/sterm.rc", home);
5054 if (loadConfig(configfile) == 0) goto cfgdone;
5055 free(configfile); configfile = NULL;
5058 configfile = SPrintf("/etc/sterm.rc");
5059 if (loadConfig(configfile) == 0) goto cfgdone;
5060 free(configfile); configfile = NULL;
5062 configfile = SPrintf("/etc/sterm/sterm.rc");
5063 if (loadConfig(configfile) == 0) goto cfgdone;
5064 free(configfile); configfile = NULL;
5066 configfile = SPrintf("./.sterm.rc");
5067 if (loadConfig(configfile) == 0) goto cfgdone;
5068 free(configfile); configfile = NULL;
5069 // no config
5070 } else {
5071 if (loadConfig(configfile) < 0) die("config file '%s' not found!", configfile);
5073 cfgdone:
5074 if (configfile != NULL) free(configfile); configfile = NULL;
5076 for (int f = 1; f < argc; f++) {
5077 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
5078 if (strcmp(argv[f], "-into") == 0 || strcmp(argv[f], "-embed") == 0) {
5079 if (opt_embed) free(opt_embed);
5080 opt_embed = strdup(argv[f]);
5081 continue;
5083 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
5084 case 't':
5085 if (++f < argc) {
5086 free(opt_title);
5087 opt_title = strdup(argv[f]);
5089 break;
5090 case 'c':
5091 if (++f < argc) {
5092 free(opt_class);
5093 opt_class = strdup(argv[f]);
5095 break;
5096 case 'w':
5097 if (++f < argc) {
5098 if (opt_embed) free(opt_embed);
5099 opt_embed = strdup(argv[f]);
5101 break;
5102 case 'e':
5103 /* eat every remaining arguments */
5104 if (++f < argc) opt_cmd = &argv[f];
5105 f = argc+1;
5106 case 'T':
5107 case 'C':
5108 case 'l':
5109 ++f;
5110 break;
5111 case 'S':
5112 break;
5113 case 'v':
5114 case 'h':
5115 default:
5116 fprintf(stderr, "%s", USAGE);
5117 exit(EXIT_FAILURE);
5121 setenv("TERM", opt_term, 1);
5122 mclock_init();
5123 setlocale(LC_ALL, "");
5124 initLCConversion();
5125 updateTabBar = 1;
5126 termidx = 0;
5127 term = termalloc();
5128 if (term->execcmd != NULL) { free(term->execcmd); term->execcmd = NULL; }
5129 tinitialize(80, 25);
5130 if (ttynew(term) != 0) die("can't run process");
5131 opt_cmd = NULL;
5132 xinit();
5133 selinit();
5134 run();
5135 return 0;