3 #elif defined(__APPLE__)
4 #define _DARWIN_C_SOURCE
5 #elif defined(__FreeBSD__)
6 #define __BSD_VISIBLE 1
10 #include <sys/ioctl.h>
11 #include <sys/select.h>
27 static int inputmode
= TB_INPUT_ESC
;
29 /* bytebuffer.inl --------------------------------------------------------- */
38 bytebuffer_reserve(struct bytebuffer
*b
, int cap
)
44 // prefer doubling capacity
45 if (b
->cap
* 2 >= cap
) {
49 char *newbuf
= realloc(b
->buf
, cap
);
55 bytebuffer_init(struct bytebuffer
*b
, int cap
)
63 b
->buf
= malloc(cap
); // just assume malloc works always
68 bytebuffer_free(struct bytebuffer
*b
)
75 bytebuffer_clear(struct bytebuffer
*b
)
81 bytebuffer_append(struct bytebuffer
*b
, const char *data
, int len
)
83 bytebuffer_reserve(b
, b
->len
+ len
);
84 memcpy(b
->buf
+ b
->len
, data
, len
);
89 bytebuffer_puts(struct bytebuffer
*b
, const char *str
)
91 bytebuffer_append(b
, str
, strlen(str
));
95 bytebuffer_resize(struct bytebuffer
*b
, int len
)
97 bytebuffer_reserve(b
, len
);
102 bytebuffer_flush(struct bytebuffer
*b
, int fd
)
104 (void)write(fd
, b
->buf
, b
->len
);
109 bytebuffer_truncate(struct bytebuffer
*b
, int n
)
115 const int nmove
= b
->len
- n
;
116 memmove(b
->buf
, b
->buf
+ n
, nmove
);
120 /* term.inl --------------------------------------------------------------- */
139 #define ENTER_MOUSE_SEQ "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h"
140 #define EXIT_MOUSE_SEQ "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l"
142 #define EUNSUPPORTED_TERM -1
145 static const char *rxvt_256color_keys
[] = { "\033[11~", "\033[12~", "\033[13~",
146 "\033[14~", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~",
147 "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[7~",
148 "\033[8~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C",
150 static const char *rxvt_256color_funcs
[] = {
152 "\033[2J\033[?47l\0338",
168 static const char *eterm_keys
[] = { "\033[11~", "\033[12~", "\033[13~",
169 "\033[14~", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~",
170 "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[7~",
171 "\033[8~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C",
173 static const char *eterm_funcs
[] = {
175 "\033[2J\033[?47l\0338",
191 static const char *screen_keys
[] = { "\033OP", "\033OQ", "\033OR", "\033OS",
192 "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~",
193 "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[1~", "\033[4~",
194 "\033[5~", "\033[6~", "\033OA", "\033OB", "\033OD", "\033OC", 0 };
195 static const char *screen_funcs
[] = {
213 static const char *rxvt_unicode_keys
[] = { "\033[11~", "\033[12~", "\033[13~",
214 "\033[14~", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~",
215 "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[7~",
216 "\033[8~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C",
218 static const char *rxvt_unicode_funcs
[] = {
236 static const char *linux_keys
[] = { "\033[[A", "\033[[B", "\033[[C", "\033[[D",
237 "\033[[E", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~",
238 "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[1~", "\033[4~",
239 "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C", 0 };
240 static const char *linux_funcs
[] = {
258 static const char *xterm_keys
[] = { "\033OP", "\033OQ", "\033OR", "\033OS",
259 "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~",
260 "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033OH", "\033OF",
261 "\033[5~", "\033[6~", "\033OA", "\033OB", "\033OD", "\033OC", 0 };
262 static const char *xterm_funcs
[] = {
265 "\033[?12l\033[?25h",
284 { "rxvt-256color", rxvt_256color_keys
, rxvt_256color_funcs
},
285 { "Eterm", eterm_keys
, eterm_funcs
},
286 { "screen", screen_keys
, screen_funcs
},
287 { "rxvt-unicode", rxvt_unicode_keys
, rxvt_unicode_funcs
},
288 { "linux", linux_keys
, linux_funcs
},
289 { "xterm", xterm_keys
, xterm_funcs
},
293 static bool init_from_terminfo
= false;
294 static const char **keys
;
295 static const char **funcs
;
298 try_compatible(const char *term
, const char *name
, const char **tkeys
,
301 if (strstr(term
, name
)) {
307 return EUNSUPPORTED_TERM
;
311 init_term_builtin(void)
314 const char *term
= getenv("TERM");
317 for (i
= 0; terms
[i
].name
; i
++) {
318 if (!strcmp(terms
[i
].name
, term
)) {
319 keys
= terms
[i
].keys
;
320 funcs
= terms
[i
].funcs
;
325 /* let's do some heuristic, maybe it's a compatible terminal */
326 if (try_compatible(term
, "xterm", xterm_keys
, xterm_funcs
) == 0)
328 if (try_compatible(term
, "rxvt", rxvt_unicode_keys
,
329 rxvt_unicode_funcs
) == 0)
331 if (try_compatible(term
, "linux", linux_keys
, linux_funcs
) == 0)
333 if (try_compatible(term
, "Eterm", eterm_keys
, eterm_funcs
) == 0)
335 if (try_compatible(term
, "screen", screen_keys
, screen_funcs
) ==
338 if (try_compatible(term
, "tmux", screen_keys
, screen_funcs
) ==
341 /* let's assume that 'cygwin' is xterm compatible */
342 if (try_compatible(term
, "cygwin", xterm_keys
, xterm_funcs
) ==
347 return EUNSUPPORTED_TERM
;
350 //----------------------------------------------------------------------
352 //----------------------------------------------------------------------
355 read_file(const char *file
)
357 FILE *f
= fopen(file
, "rb");
362 if (fstat(fileno(f
), &st
) != 0) {
367 char *data
= malloc(st
.st_size
);
373 if (fread(data
, 1, st
.st_size
, f
) != (size_t)st
.st_size
) {
384 terminfo_try_path(const char *path
, const char *term
)
387 snprintf(tmp
, sizeof(tmp
), "%s/%c/%s", path
, term
[0], term
);
388 tmp
[sizeof(tmp
) - 1] = '\0';
389 char *data
= read_file(tmp
);
394 // fallback to darwin specific dirs structure
395 snprintf(tmp
, sizeof(tmp
), "%s/%x/%s", path
, term
[0], term
);
396 tmp
[sizeof(tmp
) - 1] = '\0';
397 return read_file(tmp
);
404 const char *term
= getenv("TERM");
409 // if TERMINFO is set, no other directory should be searched
410 const char *terminfo
= getenv("TERMINFO");
412 return terminfo_try_path(terminfo
, term
);
415 // next, consider ~/.terminfo
416 const char *home
= getenv("HOME");
418 snprintf(tmp
, sizeof(tmp
), "%s/.terminfo", home
);
419 tmp
[sizeof(tmp
) - 1] = '\0';
420 char *data
= terminfo_try_path(tmp
, term
);
425 // next, TERMINFO_DIRS
426 const char *dirs
= getenv("TERMINFO_DIRS");
428 snprintf(tmp
, sizeof(tmp
), "%s", dirs
);
429 tmp
[sizeof(tmp
) - 1] = '\0';
430 char *dir
= strtok(tmp
, ":");
432 const char *cdir
= dir
;
433 if (strcmp(cdir
, "") == 0) {
434 cdir
= "/usr/share/terminfo";
436 char *data
= terminfo_try_path(cdir
, term
);
439 dir
= strtok(0, ":");
443 // fallback to /usr/share/terminfo
444 return terminfo_try_path("/usr/share/terminfo", term
);
447 #define TI_MAGIC 0432
448 #define TI_ALT_MAGIC 542
449 #define TI_HEADER_LENGTH 12
450 #define TB_KEYS_NUM 22
453 terminfo_copy_string(char *data
, int str
, int table
)
455 const int16_t off
= *(int16_t *)(data
+ str
);
456 const char *src
= data
+ table
+ off
;
457 int len
= strlen(src
);
458 char *dst
= malloc(len
+ 1);
463 static const int16_t ti_funcs
[] = {
478 static const int16_t ti_keys
[] = {
480 68 /* apparently not a typo; 67 is F10 for whatever reason */,
507 char *data
= load_terminfo();
509 init_from_terminfo
= false;
510 return init_term_builtin();
513 int16_t *header
= (int16_t *)data
;
515 const int number_sec_len
= header
[0] == TI_ALT_MAGIC
? 4 : 2;
517 if ((header
[1] + header
[2]) % 2) {
518 // old quirk to align everything on word boundaries
522 const int str_offset
= TI_HEADER_LENGTH
+ header
[1] + header
[2] +
523 number_sec_len
* header
[3];
524 const int table_offset
= str_offset
+ 2 * header
[4];
526 keys
= malloc(sizeof(const char *) * (TB_KEYS_NUM
+ 1));
527 for (i
= 0; i
< TB_KEYS_NUM
; i
++) {
528 keys
[i
] = terminfo_copy_string(
529 data
, str_offset
+ 2 * ti_keys
[i
], table_offset
);
531 keys
[TB_KEYS_NUM
] = 0;
533 funcs
= malloc(sizeof(const char *) * T_FUNCS_NUM
);
534 // the last two entries are reserved for mouse. because the table offset is
535 // not there, the two entries have to fill in manually
536 for (i
= 0; i
< T_FUNCS_NUM
- 2; i
++) {
537 funcs
[i
] = terminfo_copy_string(
538 data
, str_offset
+ 2 * ti_funcs
[i
], table_offset
);
541 funcs
[T_FUNCS_NUM
- 2] = ENTER_MOUSE_SEQ
;
542 funcs
[T_FUNCS_NUM
- 1] = EXIT_MOUSE_SEQ
;
544 init_from_terminfo
= true;
552 if (init_from_terminfo
) {
554 for (i
= 0; i
< TB_KEYS_NUM
; i
++) {
555 free((void *)keys
[i
]);
557 // the last two entries are reserved for mouse. because the table offset
558 // is not there, the two entries have to fill in manually and do not
560 for (i
= 0; i
< T_FUNCS_NUM
- 2; i
++) {
561 free((void *)funcs
[i
]);
568 /* input.inl -------------------------------------------------------------- */
570 // if s1 starts with s2 returns true, else false
571 // len is the length of s1
572 // s2 should be null-terminated
574 starts_with(const char *s1
, int len
, const char *s2
)
577 while (*s2
&& n
< len
) {
585 // convert escape sequence to event, and return consumed bytes on success (failure == 0)
587 parse_escape_seq(struct tb_event
*event
, const char *buf
, int len
)
589 // it's pretty simple here, find 'starts_with' match and return
590 // success, else return failure
592 for (i
= 0; keys
[i
]; i
++) {
593 if (starts_with(buf
, len
, keys
[i
])) {
595 event
->key
= 0xFFFF - i
;
596 return strlen(keys
[i
]);
603 extract_event(struct tb_event
*event
, struct bytebuffer
*inbuf
)
605 const char *buf
= inbuf
->buf
;
606 const int len
= inbuf
->len
;
610 if (buf
[0] == '\033') {
611 int n
= parse_escape_seq(event
, buf
, len
);
618 bytebuffer_truncate(inbuf
, n
);
621 // it's not escape sequence, then it's ALT or ESC,
623 if (inputmode
& TB_INPUT_ESC
) {
624 // if we're in escape mode, fill ESC event, pop
625 // buffer, return success
627 event
->key
= TB_KEY_ESC
;
629 bytebuffer_truncate(inbuf
, 1);
631 } else if (inputmode
& TB_INPUT_ALT
) {
632 // if we're in alt mode, set ALT modifier to
633 // event and redo parsing
634 event
->mod
= TB_MOD_ALT
;
635 bytebuffer_truncate(inbuf
, 1);
636 return extract_event(event
, inbuf
);
638 assert(!"never got here");
642 // if we're here, this is not an escape sequence and not an alt sequence
643 // so, it's a FUNCTIONAL KEY or a UNICODE character
645 // first of all check if it's a functional key
646 if ((unsigned char)buf
[0] <= TB_KEY_SPACE
||
647 (unsigned char)buf
[0] == TB_KEY_BACKSPACE2
) {
648 // fill event, pop buffer, return success */
650 event
->key
= (uint16_t)buf
[0];
651 bytebuffer_truncate(inbuf
, 1);
655 // feh... we got utf8 here
657 // check if there is all bytes
658 if (len
>= tb_utf8_char_length(buf
[0])) {
659 /* everything ok, fill event, pop buffer, return success */
660 tb_utf8_char_to_unicode(&event
->ch
, buf
);
662 bytebuffer_truncate(inbuf
, tb_utf8_char_length(buf
[0]));
666 // event isn't recognized, perhaps there is not enough bytes in utf8
671 /* -------------------------------------------------------- */
676 struct tb_cell
*cells
;
679 #define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)]
680 #define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1)
681 #define LAST_COORD_INIT -1
683 static struct termios orig_tios
;
685 static struct cellbuf back_buffer
;
686 static struct cellbuf front_buffer
;
687 static struct bytebuffer output_buffer
;
688 static struct bytebuffer input_buffer
;
690 static int termw
= -1;
691 static int termh
= -1;
693 static int outputmode
= TB_OUTPUT_NORMAL
;
696 static int winch_fds
[2];
698 static int lastx
= LAST_COORD_INIT
;
699 static int lasty
= LAST_COORD_INIT
;
700 static int cursor_x
= -1;
701 static int cursor_y
= -1;
703 static uint16_t background
= TB_DEFAULT
;
704 static uint16_t foreground
= TB_DEFAULT
;
706 static void write_cursor(int x
, int y
);
707 static void write_sgr(uint16_t fg
, uint16_t bg
);
709 static void cellbuf_init(struct cellbuf
*buf
, int width
, int height
);
710 static void cellbuf_resize(struct cellbuf
*buf
, int width
, int height
);
711 static void cellbuf_clear(struct cellbuf
*buf
);
712 static void cellbuf_free(struct cellbuf
*buf
);
714 static void update_size(void);
715 static void update_term_size(void);
716 static void send_attr(uint16_t fg
, uint16_t bg
);
717 static void send_char(int x
, int y
, uint32_t c
);
718 static void send_clear(void);
719 static void sigwinch_handler(int xxx
);
720 static int wait_fill_event(struct tb_event
*event
, struct timeval
*timeout
);
722 /* may happen in a different thread */
723 static volatile int buffer_size_change_request
;
725 /* -------------------------------------------------------- */
728 tb_init_fd(int inout_
)
732 return TB_EFAILED_TO_OPEN_TTY
;
735 if (init_term() < 0) {
737 return TB_EUNSUPPORTED_TERMINAL
;
740 if (pipe(winch_fds
) < 0) {
742 return TB_EPIPE_TRAP_ERROR
;
746 memset(&sa
, 0, sizeof(sa
));
747 sa
.sa_handler
= sigwinch_handler
;
749 sigaction(SIGWINCH
, &sa
, 0);
751 tcgetattr(inout
, &orig_tios
);
754 memcpy(&tios
, &orig_tios
, sizeof(tios
));
756 tios
.c_iflag
&= ~(IGNBRK
| BRKINT
| PARMRK
| ISTRIP
| INLCR
| IGNCR
|
758 tios
.c_oflag
&= ~OPOST
;
759 tios
.c_lflag
&= ~(ECHO
| ECHONL
| ICANON
| ISIG
| IEXTEN
);
760 tios
.c_cflag
&= ~(CSIZE
| PARENB
);
763 tios
.c_cc
[VTIME
] = 0;
764 tcsetattr(inout
, TCSAFLUSH
, &tios
);
766 bytebuffer_init(&input_buffer
, 128);
767 bytebuffer_init(&output_buffer
, 32 * 1024);
769 bytebuffer_puts(&output_buffer
, funcs
[T_ENTER_CA
]);
770 bytebuffer_puts(&output_buffer
, funcs
[T_ENTER_KEYPAD
]);
771 bytebuffer_puts(&output_buffer
, funcs
[T_HIDE_CURSOR
]);
775 cellbuf_init(&back_buffer
, termw
, termh
);
776 cellbuf_init(&front_buffer
, termw
, termh
);
777 cellbuf_clear(&back_buffer
);
778 cellbuf_clear(&front_buffer
);
784 tb_init_file(const char *name
)
786 return tb_init_fd(open(name
, O_RDWR
));
792 return tb_init_file("/dev/tty");
799 fputs("tb_shutdown() should not be called twice.", stderr
);
803 bytebuffer_puts(&output_buffer
, funcs
[T_SHOW_CURSOR
]);
804 bytebuffer_puts(&output_buffer
, funcs
[T_SGR0
]);
805 bytebuffer_puts(&output_buffer
, funcs
[T_CLEAR_SCREEN
]);
806 bytebuffer_puts(&output_buffer
, funcs
[T_EXIT_CA
]);
807 bytebuffer_puts(&output_buffer
, funcs
[T_EXIT_KEYPAD
]);
808 bytebuffer_puts(&output_buffer
, funcs
[T_EXIT_MOUSE
]);
809 bytebuffer_flush(&output_buffer
, inout
);
810 tcsetattr(inout
, TCSAFLUSH
, &orig_tios
);
817 cellbuf_free(&back_buffer
);
818 cellbuf_free(&front_buffer
);
819 bytebuffer_free(&output_buffer
);
820 bytebuffer_free(&input_buffer
);
828 struct tb_cell
*back
, *front
;
830 /* invalidate cursor position */
831 lastx
= LAST_COORD_INIT
;
832 lasty
= LAST_COORD_INIT
;
834 if (buffer_size_change_request
) {
836 buffer_size_change_request
= 0;
839 for (y
= 0; y
< front_buffer
.height
; ++y
) {
840 for (x
= 0; x
< front_buffer
.width
;) {
841 back
= &CELL(&back_buffer
, x
, y
);
842 front
= &CELL(&front_buffer
, x
, y
);
843 w
= wcwidth(back
->ch
);
846 if (memcmp(back
, front
, sizeof(struct tb_cell
)) == 0) {
850 memcpy(front
, back
, sizeof(struct tb_cell
));
851 send_attr(back
->fg
, back
->bg
);
852 if (w
> 1 && x
>= front_buffer
.width
- (w
- 1)) {
853 // Not enough room for wide ch, so send spaces
854 for (i
= x
; i
< front_buffer
.width
; ++i
) {
855 send_char(i
, y
, ' ');
858 send_char(x
, y
, back
->ch
);
859 for (i
= 1; i
< w
; ++i
) {
860 front
= &CELL(&front_buffer
, x
+ i
, y
);
862 front
->fg
= back
->fg
;
863 front
->bg
= back
->bg
;
869 if (!IS_CURSOR_HIDDEN(cursor_x
, cursor_y
))
870 write_cursor(cursor_x
, cursor_y
);
871 bytebuffer_flush(&output_buffer
, inout
);
875 tb_set_cursor(int cx
, int cy
)
877 if (IS_CURSOR_HIDDEN(cursor_x
, cursor_y
) && !IS_CURSOR_HIDDEN(cx
, cy
))
878 bytebuffer_puts(&output_buffer
, funcs
[T_SHOW_CURSOR
]);
880 if (!IS_CURSOR_HIDDEN(cursor_x
, cursor_y
) && IS_CURSOR_HIDDEN(cx
, cy
))
881 bytebuffer_puts(&output_buffer
, funcs
[T_HIDE_CURSOR
]);
885 if (!IS_CURSOR_HIDDEN(cursor_x
, cursor_y
))
886 write_cursor(cursor_x
, cursor_y
);
890 tb_put_cell(int x
, int y
, const struct tb_cell
*cell
)
892 if ((unsigned)x
>= (unsigned)back_buffer
.width
)
894 if ((unsigned)y
>= (unsigned)back_buffer
.height
)
896 CELL(&back_buffer
, x
, y
) = *cell
;
900 tb_change_cell(int x
, int y
, uint32_t ch
, uint16_t fg
, uint16_t bg
)
902 struct tb_cell c
= { ch
, fg
, bg
};
903 tb_put_cell(x
, y
, &c
);
907 tb_blit(int x
, int y
, int w
, int h
, const struct tb_cell
*cells
)
909 if (x
+ w
< 0 || x
>= back_buffer
.width
)
911 if (y
+ h
< 0 || y
>= back_buffer
.height
)
913 int xo
= 0, yo
= 0, ww
= w
, hh
= h
;
924 if (ww
> back_buffer
.width
- x
)
925 ww
= back_buffer
.width
- x
;
926 if (hh
> back_buffer
.height
- y
)
927 hh
= back_buffer
.height
- y
;
930 struct tb_cell
*dst
= &CELL(&back_buffer
, x
, y
);
931 const struct tb_cell
*src
= cells
+ yo
* w
+ xo
;
932 size_t size
= sizeof(struct tb_cell
) * ww
;
934 for (sy
= 0; sy
< hh
; ++sy
) {
935 memcpy(dst
, src
, size
);
936 dst
+= back_buffer
.width
;
944 return back_buffer
.cells
;
948 tb_poll_event(struct tb_event
*event
)
950 return wait_fill_event(event
, 0);
954 tb_peek_event(struct tb_event
*event
, int timeout
)
957 tv
.tv_sec
= timeout
/ 1000;
958 tv
.tv_usec
= (timeout
- (tv
.tv_sec
* 1000)) * 1000;
959 return wait_fill_event(event
, &tv
);
977 if (buffer_size_change_request
) {
979 buffer_size_change_request
= 0;
981 cellbuf_clear(&back_buffer
);
985 tb_select_input_mode(int mode
)
988 if ((mode
& (TB_INPUT_ESC
| TB_INPUT_ALT
)) == 0)
989 mode
|= TB_INPUT_ESC
;
991 /* technically termbox can handle that, but let's be nice and show here
992 what mode is actually used */
993 if ((mode
& (TB_INPUT_ESC
| TB_INPUT_ALT
)) ==
994 (TB_INPUT_ESC
| TB_INPUT_ALT
))
995 mode
&= ~TB_INPUT_ALT
;
998 if (mode
& TB_INPUT_MOUSE
) {
999 bytebuffer_puts(&output_buffer
, funcs
[T_ENTER_MOUSE
]);
1000 bytebuffer_flush(&output_buffer
, inout
);
1002 bytebuffer_puts(&output_buffer
, funcs
[T_EXIT_MOUSE
]);
1003 bytebuffer_flush(&output_buffer
, inout
);
1010 tb_select_output_mode(int mode
)
1018 tb_set_clear_attributes(uint16_t fg
, uint16_t bg
)
1024 /* -------------------------------------------------------- */
1027 convertnum(uint32_t num
, char *buf
)
1032 buf
[l
++] = '0' + (num
% 10);
1035 for (i
= 0; i
< l
/ 2; i
++) {
1037 buf
[i
] = buf
[l
- 1 - i
];
1038 buf
[l
- 1 - i
] = ch
;
1043 #define WRITE_LITERAL(X) bytebuffer_append(&output_buffer, (X), sizeof(X) - 1)
1044 #define WRITE_INT(X) \
1045 bytebuffer_append(&output_buffer, buf, convertnum((X), buf))
1048 write_cursor(int x
, int y
)
1051 WRITE_LITERAL("\033[");
1059 write_sgr(uint16_t fg
, uint16_t bg
)
1063 if (fg
== TB_DEFAULT
&& bg
== TB_DEFAULT
)
1066 switch (outputmode
) {
1069 case TB_OUTPUT_GRAYSCALE
:
1070 WRITE_LITERAL("\033[");
1071 if (fg
!= TB_DEFAULT
) {
1072 WRITE_LITERAL("38;5;");
1074 if (bg
!= TB_DEFAULT
) {
1078 if (bg
!= TB_DEFAULT
) {
1079 WRITE_LITERAL("48;5;");
1084 case TB_OUTPUT_NORMAL
:
1086 WRITE_LITERAL("\033[");
1087 if (fg
!= TB_DEFAULT
) {
1090 if (bg
!= TB_DEFAULT
) {
1094 if (bg
!= TB_DEFAULT
) {
1104 cellbuf_init(struct cellbuf
*buf
, int width
, int height
)
1106 buf
->cells
= (struct tb_cell
*)malloc(
1107 sizeof(struct tb_cell
) * width
* height
);
1110 buf
->height
= height
;
1114 cellbuf_resize(struct cellbuf
*buf
, int width
, int height
)
1116 if (buf
->width
== width
&& buf
->height
== height
)
1119 int oldw
= buf
->width
;
1120 int oldh
= buf
->height
;
1121 struct tb_cell
*oldcells
= buf
->cells
;
1123 cellbuf_init(buf
, width
, height
);
1126 int minw
= (width
< oldw
) ? width
: oldw
;
1127 int minh
= (height
< oldh
) ? height
: oldh
;
1130 for (i
= 0; i
< minh
; ++i
) {
1131 struct tb_cell
*csrc
= oldcells
+ (i
* oldw
);
1132 struct tb_cell
*cdst
= buf
->cells
+ (i
* width
);
1133 memcpy(cdst
, csrc
, sizeof(struct tb_cell
) * minw
);
1140 cellbuf_clear(struct cellbuf
*buf
)
1143 int ncells
= buf
->width
* buf
->height
;
1145 for (i
= 0; i
< ncells
; ++i
) {
1146 buf
->cells
[i
].ch
= ' ';
1147 buf
->cells
[i
].fg
= foreground
;
1148 buf
->cells
[i
].bg
= background
;
1153 cellbuf_free(struct cellbuf
*buf
)
1159 get_term_size(int *w
, int *h
)
1162 memset(&sz
, 0, sizeof(sz
));
1164 ioctl(inout
, TIOCGWINSZ
, &sz
);
1173 update_term_size(void)
1176 memset(&sz
, 0, sizeof(sz
));
1178 ioctl(inout
, TIOCGWINSZ
, &sz
);
1185 send_attr(uint16_t fg
, uint16_t bg
)
1187 #define LAST_ATTR_INIT 0xFFFF
1188 static uint16_t lastfg
= LAST_ATTR_INIT
, lastbg
= LAST_ATTR_INIT
;
1189 if (fg
!= lastfg
|| bg
!= lastbg
) {
1190 bytebuffer_puts(&output_buffer
, funcs
[T_SGR0
]);
1195 switch (outputmode
) {
1212 case TB_OUTPUT_GRAYSCALE
:
1223 case TB_OUTPUT_NORMAL
:
1230 bytebuffer_puts(&output_buffer
, funcs
[T_BOLD
]);
1232 bytebuffer_puts(&output_buffer
, funcs
[T_BLINK
]);
1233 if (fg
& TB_UNDERLINE
)
1234 bytebuffer_puts(&output_buffer
, funcs
[T_UNDERLINE
]);
1235 if ((fg
& TB_REVERSE
) || (bg
& TB_REVERSE
))
1236 bytebuffer_puts(&output_buffer
, funcs
[T_REVERSE
]);
1238 write_sgr(fgcol
, bgcol
);
1246 send_char(int x
, int y
, uint32_t c
)
1249 int bw
= tb_utf8_unicode_to_char(buf
, c
);
1250 if (x
- 1 != lastx
|| y
!= lasty
)
1255 buf
[0] = ' '; // replace 0 with whitespace
1256 bytebuffer_append(&output_buffer
, buf
, bw
);
1262 send_attr(foreground
, background
);
1263 bytebuffer_puts(&output_buffer
, funcs
[T_CLEAR_SCREEN
]);
1264 if (!IS_CURSOR_HIDDEN(cursor_x
, cursor_y
))
1265 write_cursor(cursor_x
, cursor_y
);
1266 bytebuffer_flush(&output_buffer
, inout
);
1268 /* we need to invalidate cursor position too and these two vars are
1269 * used only for simple cursor positioning optimization, cursor
1270 * actually may be in the correct place, but we simply discard
1271 * optimization once and it gives us simple solution for the case when
1273 lastx
= LAST_COORD_INIT
;
1274 lasty
= LAST_COORD_INIT
;
1278 sigwinch_handler(int xxx
)
1282 (void)write(winch_fds
[1], &zzz
, sizeof(int));
1289 cellbuf_resize(&back_buffer
, termw
, termh
);
1290 cellbuf_resize(&front_buffer
, termw
, termh
);
1291 cellbuf_clear(&front_buffer
);
1299 const int prevlen
= input_buffer
.len
;
1300 bytebuffer_resize(&input_buffer
, prevlen
+ n
);
1303 while (read_n
<= n
) {
1306 r
= read(inout
, input_buffer
.buf
+ prevlen
+ read_n
,
1310 // While linux man for tty says when VMIN == 0 && VTIME == 0, read
1311 // should return 0 when there is nothing to read, cygwin's read returns
1312 // -1. Not sure why and if it's correct to ignore it, but let's pretend
1318 // EAGAIN / EWOULDBLOCK shouldn't occur here
1319 assert(errno
!= EAGAIN
&& errno
!= EWOULDBLOCK
);
1324 bytebuffer_resize(&input_buffer
, prevlen
+ read_n
);
1328 assert(!"unreachable");
1333 wait_fill_event(struct tb_event
*event
, struct timeval
*timeout
)
1336 #define ENOUGH_DATA_FOR_PARSING 64
1338 memset(event
, 0, sizeof(struct tb_event
));
1340 // try to extract event from input buffer, return on success
1341 event
->type
= TB_EVENT_KEY
;
1342 if (extract_event(event
, &input_buffer
))
1345 // it looks like input buffer is incomplete, let's try the short path,
1346 // but first make sure there is enough space
1347 int n
= read_up_to(ENOUGH_DATA_FOR_PARSING
);
1350 if (n
> 0 && extract_event(event
, &input_buffer
))
1353 // n == 0, or not enough data, let's go to select
1356 FD_SET(inout
, &events
);
1357 FD_SET(winch_fds
[0], &events
);
1358 int maxfd
= (winch_fds
[0] > inout
) ? winch_fds
[0] : inout
;
1359 int result
= select(maxfd
+ 1, &events
, 0, 0, timeout
);
1363 if (FD_ISSET(inout
, &events
)) {
1364 event
->type
= TB_EVENT_KEY
;
1365 n
= read_up_to(ENOUGH_DATA_FOR_PARSING
);
1372 if (extract_event(event
, &input_buffer
))
1375 if (FD_ISSET(winch_fds
[0], &events
)) {
1376 event
->type
= TB_EVENT_RESIZE
;
1378 (void)read(winch_fds
[0], &zzz
, sizeof(int));
1379 buffer_size_change_request
= 1;
1380 get_term_size(&event
->w
, &event
->h
);
1381 return TB_EVENT_RESIZE
;