[ref] add PERROR macro
[sfm.git] / termbox.c
blob07bdb3b8e44cf4369b6b72402ca411b2c64d93b7
1 #if defined(__linux__)
2 #define _GNU_SOURCE
3 #elif defined(__APPLE__)
4 #define _DARWIN_C_SOURCE
5 #elif defined(__FreeBSD__)
6 #define __BSD_VISIBLE 1
7 #endif
8 #include "termbox.h"
10 #include <sys/ioctl.h>
11 #include <sys/select.h>
12 #include <sys/stat.h>
13 #include <sys/time.h>
15 #include <assert.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <signal.h>
19 #include <stdbool.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <termios.h>
24 #include <unistd.h>
25 #include <wchar.h>
27 static int inputmode = TB_INPUT_ESC;
29 /* bytebuffer.inl --------------------------------------------------------- */
31 struct bytebuffer {
32 char *buf;
33 int len;
34 int cap;
37 static void
38 bytebuffer_reserve(struct bytebuffer *b, int cap)
40 if (b->cap >= cap) {
41 return;
44 // prefer doubling capacity
45 if (b->cap * 2 >= cap) {
46 cap = b->cap * 2;
49 char *newbuf = realloc(b->buf, cap);
50 b->buf = newbuf;
51 b->cap = cap;
54 static void
55 bytebuffer_init(struct bytebuffer *b, int cap)
57 b->cap = 0;
58 b->len = 0;
59 b->buf = 0;
61 if (cap > 0) {
62 b->cap = cap;
63 b->buf = malloc(cap); // just assume malloc works always
67 static void
68 bytebuffer_free(struct bytebuffer *b)
70 if (b->buf)
71 free(b->buf);
74 static void
75 bytebuffer_clear(struct bytebuffer *b)
77 b->len = 0;
80 static void
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);
85 b->len += len;
88 static void
89 bytebuffer_puts(struct bytebuffer *b, const char *str)
91 bytebuffer_append(b, str, strlen(str));
94 static void
95 bytebuffer_resize(struct bytebuffer *b, int len)
97 bytebuffer_reserve(b, len);
98 b->len = len;
101 static void
102 bytebuffer_flush(struct bytebuffer *b, int fd)
104 (void)write(fd, b->buf, b->len);
105 bytebuffer_clear(b);
108 static void
109 bytebuffer_truncate(struct bytebuffer *b, int n)
111 if (n <= 0)
112 return;
113 if (n > b->len)
114 n = b->len;
115 const int nmove = b->len - n;
116 memmove(b->buf, b->buf + n, nmove);
117 b->len -= n;
120 /* term.inl --------------------------------------------------------------- */
121 enum {
122 T_ENTER_CA,
123 T_EXIT_CA,
124 T_SHOW_CURSOR,
125 T_HIDE_CURSOR,
126 T_CLEAR_SCREEN,
127 T_SGR0,
128 T_UNDERLINE,
129 T_BOLD,
130 T_BLINK,
131 T_REVERSE,
132 T_ENTER_KEYPAD,
133 T_EXIT_KEYPAD,
134 T_ENTER_MOUSE,
135 T_EXIT_MOUSE,
136 T_FUNCS_NUM,
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
144 // rxvt-256color
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",
149 0 };
150 static const char *rxvt_256color_funcs[] = {
151 "\0337\033[?47h",
152 "\033[2J\033[?47l\0338",
153 "\033[?25h",
154 "\033[?25l",
155 "\033[H\033[2J",
156 "\033[m",
157 "\033[4m",
158 "\033[1m",
159 "\033[5m",
160 "\033[7m",
161 "\033=",
162 "\033>",
163 ENTER_MOUSE_SEQ,
164 EXIT_MOUSE_SEQ,
167 // Eterm
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",
172 0 };
173 static const char *eterm_funcs[] = {
174 "\0337\033[?47h",
175 "\033[2J\033[?47l\0338",
176 "\033[?25h",
177 "\033[?25l",
178 "\033[H\033[2J",
179 "\033[m",
180 "\033[4m",
181 "\033[1m",
182 "\033[5m",
183 "\033[7m",
190 // screen
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[] = {
196 "\033[?1049h",
197 "\033[?1049l",
198 "\033[34h\033[?25h",
199 "\033[?25l",
200 "\033[H\033[J",
201 "\033[m",
202 "\033[4m",
203 "\033[1m",
204 "\033[5m",
205 "\033[7m",
206 "\033[?1h\033=",
207 "\033[?1l\033>",
208 ENTER_MOUSE_SEQ,
209 EXIT_MOUSE_SEQ,
212 // rxvt-unicode
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",
217 0 };
218 static const char *rxvt_unicode_funcs[] = {
219 "\033[?1049h",
220 "\033[r\033[?1049l",
221 "\033[?25h",
222 "\033[?25l",
223 "\033[H\033[2J",
224 "\033[m\033(B",
225 "\033[4m",
226 "\033[1m",
227 "\033[5m",
228 "\033[7m",
229 "\033=",
230 "\033>",
231 ENTER_MOUSE_SEQ,
232 EXIT_MOUSE_SEQ,
235 // linux
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[] = {
243 "\033[?25h\033[?0c",
244 "\033[?25l\033[?1c",
245 "\033[H\033[J",
246 "\033[0;10m",
247 "\033[4m",
248 "\033[1m",
249 "\033[5m",
250 "\033[7m",
257 // xterm
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[] = {
263 "\033[?1049h",
264 "\033[?1049l",
265 "\033[?12l\033[?25h",
266 "\033[?25l",
267 "\033[H\033[2J",
268 "\033(B\033[m",
269 "\033[4m",
270 "\033[1m",
271 "\033[5m",
272 "\033[7m",
273 "\033[?1h\033=",
274 "\033[?1l\033>",
275 ENTER_MOUSE_SEQ,
276 EXIT_MOUSE_SEQ,
279 static struct term {
280 const char *name;
281 const char **keys;
282 const char **funcs;
283 } terms[] = {
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 },
290 { 0, 0, 0 },
293 static bool init_from_terminfo = false;
294 static const char **keys;
295 static const char **funcs;
297 static int
298 try_compatible(const char *term, const char *name, const char **tkeys,
299 const char **tfuncs)
301 if (strstr(term, name)) {
302 keys = tkeys;
303 funcs = tfuncs;
304 return 0;
307 return EUNSUPPORTED_TERM;
310 static int
311 init_term_builtin(void)
313 int i;
314 const char *term = getenv("TERM");
316 if (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;
321 return 0;
325 /* let's do some heuristic, maybe it's a compatible terminal */
326 if (try_compatible(term, "xterm", xterm_keys, xterm_funcs) == 0)
327 return 0;
328 if (try_compatible(term, "rxvt", rxvt_unicode_keys,
329 rxvt_unicode_funcs) == 0)
330 return 0;
331 if (try_compatible(term, "linux", linux_keys, linux_funcs) == 0)
332 return 0;
333 if (try_compatible(term, "Eterm", eterm_keys, eterm_funcs) == 0)
334 return 0;
335 if (try_compatible(term, "screen", screen_keys, screen_funcs) ==
337 return 0;
338 if (try_compatible(term, "tmux", screen_keys, screen_funcs) ==
340 return 0;
341 /* let's assume that 'cygwin' is xterm compatible */
342 if (try_compatible(term, "cygwin", xterm_keys, xterm_funcs) ==
344 return 0;
347 return EUNSUPPORTED_TERM;
350 //----------------------------------------------------------------------
351 // terminfo
352 //----------------------------------------------------------------------
354 static char *
355 read_file(const char *file)
357 FILE *f = fopen(file, "rb");
358 if (!f)
359 return 0;
361 struct stat st;
362 if (fstat(fileno(f), &st) != 0) {
363 fclose(f);
364 return 0;
367 char *data = malloc(st.st_size);
368 if (!data) {
369 fclose(f);
370 return 0;
373 if (fread(data, 1, st.st_size, f) != (size_t)st.st_size) {
374 fclose(f);
375 free(data);
376 return 0;
379 fclose(f);
380 return data;
383 static char *
384 terminfo_try_path(const char *path, const char *term)
386 char tmp[4096];
387 snprintf(tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term);
388 tmp[sizeof(tmp) - 1] = '\0';
389 char *data = read_file(tmp);
390 if (data) {
391 return data;
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);
400 static char *
401 load_terminfo(void)
403 char tmp[4096];
404 const char *term = getenv("TERM");
405 if (!term) {
406 return 0;
409 // if TERMINFO is set, no other directory should be searched
410 const char *terminfo = getenv("TERMINFO");
411 if (terminfo) {
412 return terminfo_try_path(terminfo, term);
415 // next, consider ~/.terminfo
416 const char *home = getenv("HOME");
417 if (home) {
418 snprintf(tmp, sizeof(tmp), "%s/.terminfo", home);
419 tmp[sizeof(tmp) - 1] = '\0';
420 char *data = terminfo_try_path(tmp, term);
421 if (data)
422 return data;
425 // next, TERMINFO_DIRS
426 const char *dirs = getenv("TERMINFO_DIRS");
427 if (dirs) {
428 snprintf(tmp, sizeof(tmp), "%s", dirs);
429 tmp[sizeof(tmp) - 1] = '\0';
430 char *dir = strtok(tmp, ":");
431 while (dir) {
432 const char *cdir = dir;
433 if (strcmp(cdir, "") == 0) {
434 cdir = "/usr/share/terminfo";
436 char *data = terminfo_try_path(cdir, term);
437 if (data)
438 return data;
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
452 static const char *
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);
459 strcpy(dst, src);
460 return dst;
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 */,
489 216,
490 217,
494 164,
503 static int
504 init_term(void)
506 int i;
507 char *data = load_terminfo();
508 if (!data) {
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
519 header[2] += 1;
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;
545 free(data);
546 return 0;
549 static void
550 shutdown_term(void)
552 if (init_from_terminfo) {
553 int i;
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
559 // need to be freed.
560 for (i = 0; i < T_FUNCS_NUM - 2; i++) {
561 free((void *)funcs[i]);
563 free(keys);
564 free(funcs);
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
573 static bool
574 starts_with(const char *s1, int len, const char *s2)
576 int n = 0;
577 while (*s2 && n < len) {
578 if (*s1++ != *s2++)
579 return false;
580 n++;
582 return *s2 == 0;
585 // convert escape sequence to event, and return consumed bytes on success (failure == 0)
586 static int
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
591 int i;
592 for (i = 0; keys[i]; i++) {
593 if (starts_with(buf, len, keys[i])) {
594 event->ch = 0;
595 event->key = 0xFFFF - i;
596 return strlen(keys[i]);
599 return 0;
602 static bool
603 extract_event(struct tb_event *event, struct bytebuffer *inbuf)
605 const char *buf = inbuf->buf;
606 const int len = inbuf->len;
607 if (len == 0)
608 return false;
610 if (buf[0] == '\033') {
611 int n = parse_escape_seq(event, buf, len);
612 if (n != 0) {
613 bool success = true;
614 if (n < 0) {
615 success = false;
616 n = -n;
618 bytebuffer_truncate(inbuf, n);
619 return success;
620 } else {
621 // it's not escape sequence, then it's ALT or ESC,
622 // check inputmode
623 if (inputmode & TB_INPUT_ESC) {
624 // if we're in escape mode, fill ESC event, pop
625 // buffer, return success
626 event->ch = 0;
627 event->key = TB_KEY_ESC;
628 event->mod = 0;
629 bytebuffer_truncate(inbuf, 1);
630 return true;
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 */
649 event->ch = 0;
650 event->key = (uint16_t)buf[0];
651 bytebuffer_truncate(inbuf, 1);
652 return true;
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);
661 event->key = 0;
662 bytebuffer_truncate(inbuf, tb_utf8_char_length(buf[0]));
663 return true;
666 // event isn't recognized, perhaps there is not enough bytes in utf8
667 // sequence
668 return false;
671 /* -------------------------------------------------------- */
673 struct cellbuf {
674 int width;
675 int height;
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;
695 static int inout;
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_)
730 inout = inout_;
731 if (inout == -1) {
732 return TB_EFAILED_TO_OPEN_TTY;
735 if (init_term() < 0) {
736 close(inout);
737 return TB_EUNSUPPORTED_TERMINAL;
740 if (pipe(winch_fds) < 0) {
741 close(inout);
742 return TB_EPIPE_TRAP_ERROR;
745 struct sigaction sa;
746 memset(&sa, 0, sizeof(sa));
747 sa.sa_handler = sigwinch_handler;
748 sa.sa_flags = 0;
749 sigaction(SIGWINCH, &sa, 0);
751 tcgetattr(inout, &orig_tios);
753 struct termios tios;
754 memcpy(&tios, &orig_tios, sizeof(tios));
756 tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR |
757 ICRNL | IXON);
758 tios.c_oflag &= ~OPOST;
759 tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
760 tios.c_cflag &= ~(CSIZE | PARENB);
761 tios.c_cflag |= CS8;
762 tios.c_cc[VMIN] = 0;
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]);
772 send_clear();
774 update_term_size();
775 cellbuf_init(&back_buffer, termw, termh);
776 cellbuf_init(&front_buffer, termw, termh);
777 cellbuf_clear(&back_buffer);
778 cellbuf_clear(&front_buffer);
780 return 0;
784 tb_init_file(const char *name)
786 return tb_init_fd(open(name, O_RDWR));
790 tb_init(void)
792 return tb_init_file("/dev/tty");
795 void
796 tb_shutdown(void)
798 if (termw == -1) {
799 fputs("tb_shutdown() should not be called twice.", stderr);
800 abort();
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);
812 shutdown_term();
813 close(inout);
814 close(winch_fds[0]);
815 close(winch_fds[1]);
817 cellbuf_free(&back_buffer);
818 cellbuf_free(&front_buffer);
819 bytebuffer_free(&output_buffer);
820 bytebuffer_free(&input_buffer);
821 termw = termh = -1;
824 void
825 tb_present(void)
827 int x, y, w, i;
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) {
835 update_size();
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);
844 if (w < 1)
845 w = 1;
846 if (memcmp(back, front, sizeof(struct tb_cell)) == 0) {
847 x += w;
848 continue;
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, ' ');
857 } else {
858 send_char(x, y, back->ch);
859 for (i = 1; i < w; ++i) {
860 front = &CELL(&front_buffer, x + i, y);
861 front->ch = 0;
862 front->fg = back->fg;
863 front->bg = back->bg;
866 x += w;
869 if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
870 write_cursor(cursor_x, cursor_y);
871 bytebuffer_flush(&output_buffer, inout);
874 void
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]);
883 cursor_x = cx;
884 cursor_y = cy;
885 if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
886 write_cursor(cursor_x, cursor_y);
889 void
890 tb_put_cell(int x, int y, const struct tb_cell *cell)
892 if ((unsigned)x >= (unsigned)back_buffer.width)
893 return;
894 if ((unsigned)y >= (unsigned)back_buffer.height)
895 return;
896 CELL(&back_buffer, x, y) = *cell;
899 void
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);
906 void
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)
910 return;
911 if (y + h < 0 || y >= back_buffer.height)
912 return;
913 int xo = 0, yo = 0, ww = w, hh = h;
914 if (x < 0) {
915 xo = -x;
916 ww -= xo;
917 x = 0;
919 if (y < 0) {
920 yo = -y;
921 hh -= yo;
922 y = 0;
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;
929 int sy;
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;
937 src += w;
941 struct tb_cell *
942 tb_cell_buffer(void)
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)
956 struct timeval tv;
957 tv.tv_sec = timeout / 1000;
958 tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
959 return wait_fill_event(event, &tv);
963 tb_width(void)
965 return termw;
969 tb_height(void)
971 return termh;
974 void
975 tb_clear(void)
977 if (buffer_size_change_request) {
978 update_size();
979 buffer_size_change_request = 0;
981 cellbuf_clear(&back_buffer);
985 tb_select_input_mode(int mode)
987 if (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;
997 inputmode = mode;
998 if (mode & TB_INPUT_MOUSE) {
999 bytebuffer_puts(&output_buffer, funcs[T_ENTER_MOUSE]);
1000 bytebuffer_flush(&output_buffer, inout);
1001 } else {
1002 bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]);
1003 bytebuffer_flush(&output_buffer, inout);
1006 return inputmode;
1010 tb_select_output_mode(int mode)
1012 if (mode)
1013 outputmode = mode;
1014 return outputmode;
1017 void
1018 tb_set_clear_attributes(uint16_t fg, uint16_t bg)
1020 foreground = fg;
1021 background = bg;
1024 /* -------------------------------------------------------- */
1026 static int
1027 convertnum(uint32_t num, char *buf)
1029 int i, l = 0;
1030 int ch;
1031 do {
1032 buf[l++] = '0' + (num % 10);
1033 num /= 10;
1034 } while (num);
1035 for (i = 0; i < l / 2; i++) {
1036 ch = buf[i];
1037 buf[i] = buf[l - 1 - i];
1038 buf[l - 1 - i] = ch;
1040 return l;
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))
1047 static void
1048 write_cursor(int x, int y)
1050 char buf[32];
1051 WRITE_LITERAL("\033[");
1052 WRITE_INT(y + 1);
1053 WRITE_LITERAL(";");
1054 WRITE_INT(x + 1);
1055 WRITE_LITERAL("H");
1058 static void
1059 write_sgr(uint16_t fg, uint16_t bg)
1061 char buf[32];
1063 if (fg == TB_DEFAULT && bg == TB_DEFAULT)
1064 return;
1066 switch (outputmode) {
1067 case TB_OUTPUT_256:
1068 case TB_OUTPUT_216:
1069 case TB_OUTPUT_GRAYSCALE:
1070 WRITE_LITERAL("\033[");
1071 if (fg != TB_DEFAULT) {
1072 WRITE_LITERAL("38;5;");
1073 WRITE_INT(fg);
1074 if (bg != TB_DEFAULT) {
1075 WRITE_LITERAL(";");
1078 if (bg != TB_DEFAULT) {
1079 WRITE_LITERAL("48;5;");
1080 WRITE_INT(bg);
1082 WRITE_LITERAL("m");
1083 break;
1084 case TB_OUTPUT_NORMAL:
1085 default:
1086 WRITE_LITERAL("\033[");
1087 if (fg != TB_DEFAULT) {
1088 WRITE_LITERAL("3");
1089 WRITE_INT(fg - 1);
1090 if (bg != TB_DEFAULT) {
1091 WRITE_LITERAL(";");
1094 if (bg != TB_DEFAULT) {
1095 WRITE_LITERAL("4");
1096 WRITE_INT(bg - 1);
1098 WRITE_LITERAL("m");
1099 break;
1103 static void
1104 cellbuf_init(struct cellbuf *buf, int width, int height)
1106 buf->cells = (struct tb_cell *)malloc(
1107 sizeof(struct tb_cell) * width * height);
1108 assert(buf->cells);
1109 buf->width = width;
1110 buf->height = height;
1113 static void
1114 cellbuf_resize(struct cellbuf *buf, int width, int height)
1116 if (buf->width == width && buf->height == height)
1117 return;
1119 int oldw = buf->width;
1120 int oldh = buf->height;
1121 struct tb_cell *oldcells = buf->cells;
1123 cellbuf_init(buf, width, height);
1124 cellbuf_clear(buf);
1126 int minw = (width < oldw) ? width : oldw;
1127 int minh = (height < oldh) ? height : oldh;
1128 int i;
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);
1136 free(oldcells);
1139 static void
1140 cellbuf_clear(struct cellbuf *buf)
1142 int i;
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;
1152 static void
1153 cellbuf_free(struct cellbuf *buf)
1155 free(buf->cells);
1158 static void
1159 get_term_size(int *w, int *h)
1161 struct winsize sz;
1162 memset(&sz, 0, sizeof(sz));
1164 ioctl(inout, TIOCGWINSZ, &sz);
1166 if (w)
1167 *w = sz.ws_col;
1168 if (h)
1169 *h = sz.ws_row;
1172 static void
1173 update_term_size(void)
1175 struct winsize sz;
1176 memset(&sz, 0, sizeof(sz));
1178 ioctl(inout, TIOCGWINSZ, &sz);
1180 termw = sz.ws_col;
1181 termh = sz.ws_row;
1184 static void
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]);
1192 uint16_t fgcol;
1193 uint16_t bgcol;
1195 switch (outputmode) {
1196 case TB_OUTPUT_256:
1197 fgcol = fg & 0xFF;
1198 bgcol = bg & 0xFF;
1199 break;
1201 case TB_OUTPUT_216:
1202 fgcol = fg & 0xFF;
1203 if (fgcol > 215)
1204 fgcol = 7;
1205 bgcol = bg & 0xFF;
1206 if (bgcol > 215)
1207 bgcol = 0;
1208 fgcol += 0x10;
1209 bgcol += 0x10;
1210 break;
1212 case TB_OUTPUT_GRAYSCALE:
1213 fgcol = fg & 0xFF;
1214 if (fgcol > 23)
1215 fgcol = 23;
1216 bgcol = bg & 0xFF;
1217 if (bgcol > 23)
1218 bgcol = 0;
1219 fgcol += 0xe8;
1220 bgcol += 0xe8;
1221 break;
1223 case TB_OUTPUT_NORMAL:
1224 default:
1225 fgcol = fg & 0x0F;
1226 bgcol = bg & 0x0F;
1229 if (fg & TB_BOLD)
1230 bytebuffer_puts(&output_buffer, funcs[T_BOLD]);
1231 if (bg & TB_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);
1240 lastfg = fg;
1241 lastbg = bg;
1245 static void
1246 send_char(int x, int y, uint32_t c)
1248 char buf[7];
1249 int bw = tb_utf8_unicode_to_char(buf, c);
1250 if (x - 1 != lastx || y != lasty)
1251 write_cursor(x, y);
1252 lastx = x;
1253 lasty = y;
1254 if (!c)
1255 buf[0] = ' '; // replace 0 with whitespace
1256 bytebuffer_append(&output_buffer, buf, bw);
1259 static void
1260 send_clear(void)
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
1272 * cursor moved */
1273 lastx = LAST_COORD_INIT;
1274 lasty = LAST_COORD_INIT;
1277 static void
1278 sigwinch_handler(int xxx)
1280 (void)xxx;
1281 const int zzz = 1;
1282 (void)write(winch_fds[1], &zzz, sizeof(int));
1285 static void
1286 update_size(void)
1288 update_term_size();
1289 cellbuf_resize(&back_buffer, termw, termh);
1290 cellbuf_resize(&front_buffer, termw, termh);
1291 cellbuf_clear(&front_buffer);
1292 send_clear();
1295 static int
1296 read_up_to(int n)
1298 assert(n > 0);
1299 const int prevlen = input_buffer.len;
1300 bytebuffer_resize(&input_buffer, prevlen + n);
1302 int read_n = 0;
1303 while (read_n <= n) {
1304 ssize_t r = 0;
1305 if (read_n < n) {
1306 r = read(inout, input_buffer.buf + prevlen + read_n,
1307 n - read_n);
1309 #ifdef __CYGWIN__
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
1313 // it's zero.
1314 if (r < 0)
1315 r = 0;
1316 #endif
1317 if (r < 0) {
1318 // EAGAIN / EWOULDBLOCK shouldn't occur here
1319 assert(errno != EAGAIN && errno != EWOULDBLOCK);
1320 return -1;
1321 } else if (r > 0) {
1322 read_n += r;
1323 } else {
1324 bytebuffer_resize(&input_buffer, prevlen + read_n);
1325 return read_n;
1328 assert(!"unreachable");
1329 return 0;
1332 static int
1333 wait_fill_event(struct tb_event *event, struct timeval *timeout)
1335 // ;-)
1336 #define ENOUGH_DATA_FOR_PARSING 64
1337 fd_set events;
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))
1343 return event->type;
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);
1348 if (n < 0)
1349 return -1;
1350 if (n > 0 && extract_event(event, &input_buffer))
1351 return event->type;
1353 // n == 0, or not enough data, let's go to select
1354 while (1) {
1355 FD_ZERO(&events);
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);
1360 if (!result)
1361 return 0;
1363 if (FD_ISSET(inout, &events)) {
1364 event->type = TB_EVENT_KEY;
1365 n = read_up_to(ENOUGH_DATA_FOR_PARSING);
1366 if (n < 0)
1367 return -1;
1369 if (n == 0)
1370 continue;
1372 if (extract_event(event, &input_buffer))
1373 return event->type;
1375 if (FD_ISSET(winch_fds[0], &events)) {
1376 event->type = TB_EVENT_RESIZE;
1377 int zzz = 0;
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;