💾 Archived View for gmi.noulin.net › gitRepositories › heartbeat › file › shpPackages › termbox › te… captured on 2024-06-20 at 11:55:53. Gemini links have been rewritten to link to archived content

View Raw

More Information

-=-=-=-=-=-=-

heartbeat

Log

Files

Refs

README

termbox.c (18963B)

     1 #include <assert.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <errno.h>
     5 #include <fcntl.h>
     6 #include <signal.h>
     7 #include <stdio.h>
     8 #include <stdarg.h>
     9 #include <stdbool.h>
    10 #include <sys/select.h>
    11 #include <sys/ioctl.h>
    12 #include <sys/time.h>
    13 #include <sys/stat.h>
    14 #include <termios.h>
    15 #include <unistd.h>
    16 #include <wchar.h>
    17 
    18 #include "termbox.h"
    19 #include "utf8.h"
    20 
    21 #include "bytebuffer.h"
    22 #include "term.h"
    23 #include "input.h"
    24 
    25 uint32_t tb_palette[16] = {
    26         0x000000, // BLACK
    27         0xAA2222, // RED
    28         0x22AA22, // GREEN
    29         0xAA5522, // YELLOW
    30         0x3333AA, // BLUE
    31         0xAA22AA, // MAGENTA
    32         0x22AAAA, // CYAN
    33         0xDDDDDD, // WHITE
    34         0x999999, // LIGHT BLACK
    35         0xFF5555, // LIGHT RED
    36         0x55FF55, // LIGHT GREEN
    37         0xFFFF55, // LIGHT YELLOW
    38         0x5555FF, // LIGHT BLUE
    39         0xFF55FF, // LIGHT MAGENTA
    40         0x55FFFF, // LIGHT CYAN
    41         0xFFFFFF, // LIGHT WHITE
    42 };
    43 
    44 struct cellbuf {
    45         int width;
    46         int height;
    47         struct tb_cell *cells;
    48 };
    49 
    50 #define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)]
    51 #define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1)
    52 #define LAST_COORD_INIT -1
    53 
    54 static struct termios orig_tios;
    55 
    56 static struct cellbuf back_buffer;
    57 static struct cellbuf front_buffer;
    58 static struct bytebuffer output_buffer;
    59 static struct bytebuffer input_buffer;
    60 
    61 static char print_buf[8192];
    62 
    63 static int termw = -1;
    64 static int termh = -1;
    65 
    66 static int inputmode = TB_INPUT_ESC;
    67 static int outputmode = TB_OUTPUT_NORMAL;
    68 
    69 static int inout;
    70 static int winch_fds[2];
    71 
    72 static int lastx = LAST_COORD_INIT;
    73 static int lasty = LAST_COORD_INIT;
    74 static int cursor_x = -1;
    75 static int cursor_y = -1;
    76 
    77 static uint32_t background = TB_DEFAULT;
    78 static uint32_t foreground = TB_DEFAULT;
    79 
    80 static void write_cursor(int x, int y);
    81 static void write_sgr(uint32_t fg, uint32_t bg);
    82 
    83 static void cellbuf_init(struct cellbuf *buf, int width, int height);
    84 static void cellbuf_resize(struct cellbuf *buf, int width, int height);
    85 static void cellbuf_clear(struct cellbuf *buf);
    86 static void cellbuf_free(struct cellbuf *buf);
    87 
    88 static void update_size(void);
    89 static void update_term_size(void);
    90 static void send_attr(uint32_t fg, uint32_t bg);
    91 static void send_char(int x, int y, uint32_t c);
    92 static void send_clear(void);
    93 static void sigwinch_handler(int xxx);
    94 static int wait_fill_event(struct tb_event *event, struct timeval *timeout, int sock);
    95 
    96 /* may happen in a different thread */
    97 static volatile int buffer_size_change_request;
    98 
    99 /* -------------------------------------------------------- */
   100 
   101 int tb_init_fd(int inout_)
   102 {
   103         inout = inout_;
   104         if (inout == -1) {
   105                 return TB_EFAILED_TO_OPEN_TTY;
   106         }
   107 
   108         if (init_term() < 0) {
   109                 close(inout);
   110                 return TB_EUNSUPPORTED_TERMINAL;
   111         }
   112 
   113         if (pipe(winch_fds) < 0) {
   114                 close(inout);
   115                 return TB_EPIPE_TRAP_ERROR;
   116         }
   117 
   118         struct sigaction sa = {0};
   119         sa.sa_handler = sigwinch_handler;
   120         sa.sa_flags = 0;
   121         sigaction(SIGWINCH, &sa, 0);
   122 
   123         tcgetattr(inout, &orig_tios);
   124 
   125         struct termios tios;
   126         memcpy(&tios, &orig_tios, sizeof(tios));
   127 
   128         tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
   129                            | INLCR | IGNCR | ICRNL | IXON);
   130         tios.c_oflag &= ~OPOST;
   131         tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
   132         tios.c_cflag &= ~(CSIZE | PARENB);
   133         tios.c_cflag |= CS8;
   134         tios.c_cc[VMIN] = 0;
   135         tios.c_cc[VTIME] = 0;
   136         tcsetattr(inout, TCSAFLUSH, &tios);
   137 
   138         bytebuffer_init(&input_buffer, 128);
   139         bytebuffer_init(&output_buffer, 32 * 1024);
   140 
   141         bytebuffer_puts(&output_buffer, funcs[T_ENTER_CA]);
   142         bytebuffer_puts(&output_buffer, funcs[T_ENTER_KEYPAD]);
   143         bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]);
   144         send_clear();
   145 
   146         update_term_size();
   147         cellbuf_init(&back_buffer, termw, termh);
   148         cellbuf_init(&front_buffer, termw, termh);
   149         cellbuf_clear(&back_buffer);
   150         cellbuf_clear(&front_buffer);
   151 
   152         return 0;
   153 }
   154 
   155 int tb_init_file(const char* name){
   156         return tb_init_fd(open(name, O_RDWR));
   157 }
   158 
   159 int tb_init(void)
   160 {
   161         return tb_init_file("/dev/tty");
   162 }
   163 
   164 void tb_shutdown(void)
   165 {
   166         if (termw == -1) {
   167                 fputs("tb_shutdown() should not be called twice.", stderr);
   168                 abort();
   169         }
   170 
   171         bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]);
   172         bytebuffer_puts(&output_buffer, funcs[T_SGR0]);
   173         bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]);
   174         bytebuffer_puts(&output_buffer, funcs[T_EXIT_CA]);
   175         bytebuffer_puts(&output_buffer, funcs[T_EXIT_KEYPAD]);
   176         bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]);
   177         bytebuffer_flush(&output_buffer, inout);
   178         tcsetattr(inout, TCSAFLUSH, &orig_tios);
   179 
   180         shutdown_term();
   181         close(inout);
   182         close(winch_fds[0]);
   183         close(winch_fds[1]);
   184 
   185         cellbuf_free(&back_buffer);
   186         cellbuf_free(&front_buffer);
   187         bytebuffer_free(&output_buffer);
   188         bytebuffer_free(&input_buffer);
   189         termw = termh = -1;
   190 }
   191 
   192 void tb_present(void)
   193 {
   194         int x,y,w,i;
   195         struct tb_cell *back, *front;
   196 
   197         /* invalidate cursor position */
   198         lastx = LAST_COORD_INIT;
   199         lasty = LAST_COORD_INIT;
   200 
   201         if (buffer_size_change_request) {
   202                 update_size();
   203                 buffer_size_change_request = 0;
   204         }
   205 
   206         for (y = 0; y < front_buffer.height; ++y) {
   207                 for (x = 0; x < front_buffer.width; ) {
   208                         back = &CELL(&back_buffer, x, y);
   209                         front = &CELL(&front_buffer, x, y);
   210                         w = wcwidth(back->ch);
   211                         if (w < 1) w = 1;
   212                         if (memcmp(back, front, sizeof(struct tb_cell)) == 0) {
   213                                 x += w;
   214                                 continue;
   215                         }
   216                         memcpy(front, back, sizeof(struct tb_cell));
   217                         send_attr(back->fg, back->bg);
   218                         if (w > 1 && x >= front_buffer.width - (w - 1)) {
   219                                 // Not enough room for wide ch, so send spaces
   220                                 for (i = x; i < front_buffer.width; ++i) {
   221                                         send_char(i, y, ' ');
   222                                 }
   223                         } else {
   224                                 send_char(x, y, back->ch);
   225                                 for (i = 1; i < w; ++i) {
   226                                         front = &CELL(&front_buffer, x + i, y);
   227                                         front->ch = 0;
   228                                         front->fg = back->fg;
   229                                         front->bg = back->bg;
   230                                 }
   231                         }
   232                         x += w;
   233                 }
   234         }
   235         if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
   236                 write_cursor(cursor_x, cursor_y);
   237         bytebuffer_flush(&output_buffer, inout);
   238 }
   239 
   240 void tb_set_cursor(int cx, int cy)
   241 {
   242         if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy))
   243                 bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]);
   244 
   245         if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy))
   246                 bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]);
   247 
   248         cursor_x = cx;
   249         cursor_y = cy;
   250         if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
   251                 write_cursor(cursor_x, cursor_y);
   252 }
   253 
   254 void tb_put_cell(int x, int y, const struct tb_cell *cell)
   255 {
   256         if ((unsigned)x >= (unsigned)back_buffer.width)
   257                 return;
   258         if ((unsigned)y >= (unsigned)back_buffer.height)
   259                 return;
   260         CELL(&back_buffer, x, y) = *cell;
   261 }
   262 
   263 void tb_change_cell(int x, int y, uint32_t ch, uint32_t fg, uint32_t bg)
   264 {
   265         struct tb_cell c = {ch, fg, bg};
   266         tb_put_cell(x, y, &c);
   267 }
   268 
   269 int tb_string_with_limit(int x, int y, uint32_t fg, uint32_t bg, int limit, const char *str) {
   270         uint32_t uni;
   271         int l = 0;
   272         int userX = x;
   273 
   274         while (*str && l < limit) {
   275                 if (*str == '\n') {
   276                         // new line
   277                         l = 0;
   278                         x = userX;
   279                         y++;
   280                         str++;
   281                 }
   282                 else {
   283                         str += tb_utf8_char_to_unicode(&uni, str);
   284                         tb_change_cell(x, y, uni, fg, bg);
   285                         x++;
   286                         l++;
   287                 }
   288         }
   289 
   290         return l;
   291 }
   292 
   293 int tb_string(int x, int y, uint32_t fg, uint32_t bg, const char *str) {
   294         return tb_string_with_limit(x, y, fg, bg, sizeof(print_buf), str);
   295 }
   296 
   297 int tb_stringf(int x, int y, uint32_t fg, uint32_t bg, const char *fmt, ...) {
   298         va_list vl;
   299         va_start(vl, fmt);
   300         vsnprintf(print_buf, sizeof(print_buf), fmt, vl);
   301         va_end(vl);
   302         return tb_string_with_limit(x, y, fg, bg, sizeof(print_buf), print_buf);
   303 }
   304 
   305 int tb_stringf_with_limit(int x, int y, uint32_t fg, uint32_t bg, int limit, const char *fmt, ...) {
   306         va_list vl;
   307         va_start(vl, fmt);
   308         // the string is utf8 encoded so character count is unknown and different from byte count
   309         vsnprintf(print_buf, sizeof(print_buf), fmt, vl);
   310         va_end(vl);
   311         return tb_string_with_limit(x, y, fg, bg, limit, print_buf);
   312 }
   313 
   314 void tb_blit(int x, int y, int w, int h, const struct tb_cell *cells)
   315 {
   316         if (x + w < 0 || x >= back_buffer.width)
   317                 return;
   318         if (y + h < 0 || y >= back_buffer.height)
   319                 return;
   320         int xo = 0, yo = 0, ww = w, hh = h;
   321         if (x < 0) {
   322                 xo = -x;
   323                 ww -= xo;
   324                 x = 0;
   325         }
   326         if (y < 0) {
   327                 yo = -y;
   328                 hh -= yo;
   329                 y = 0;
   330         }
   331         if (ww > back_buffer.width - x)
   332                 ww = back_buffer.width - x;
   333         if (hh > back_buffer.height - y)
   334                 hh = back_buffer.height - y;
   335 
   336         int sy;
   337         struct tb_cell *dst = &CELL(&back_buffer, x, y);
   338         const struct tb_cell *src = cells + yo * w + xo;
   339         size_t size = sizeof(struct tb_cell) * ww;
   340 
   341         for (sy = 0; sy < hh; ++sy) {
   342                 memcpy(dst, src, size);
   343                 dst += back_buffer.width;
   344                 src += w;
   345         }
   346 }
   347 
   348 struct tb_cell *tb_cell_buffer(void)
   349 {
   350         return back_buffer.cells;
   351 }
   352 
   353 int tb_poll_event(struct tb_event *event, int sock)
   354 {
   355         return wait_fill_event(event, NULL, sock);
   356 }
   357 
   358 int tb_peek_event(struct tb_event *event, int timeout)
   359 {
   360         struct timeval tv;
   361         tv.tv_sec = timeout / 1000;
   362         tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
   363         return wait_fill_event(event, &tv, 0/*sock*/);
   364 }
   365 
   366 int tb_width(void)
   367 {
   368         return termw;
   369 }
   370 
   371 int tb_height(void)
   372 {
   373         return termh;
   374 }
   375 
   376 void tb_clear(void)
   377 {
   378         if (buffer_size_change_request) {
   379                 update_size();
   380                 buffer_size_change_request = 0;
   381         }
   382         cellbuf_clear(&back_buffer);
   383 }
   384 
   385 int tb_select_input_mode(int mode)
   386 {
   387         if (mode) {
   388                 if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0)
   389                         mode |= TB_INPUT_ESC;
   390 
   391                 /* technically termbox can handle that, but let's be nice and show here
   392                    what mode is actually used */
   393                 if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT))
   394                         mode &= ~TB_INPUT_ALT;
   395 
   396                 inputmode = mode;
   397                 if (mode&TB_INPUT_MOUSE) {
   398                         bytebuffer_puts(&output_buffer, funcs[T_ENTER_MOUSE]);
   399                         bytebuffer_flush(&output_buffer, inout);
   400                 } else {
   401                         bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]);
   402                         bytebuffer_flush(&output_buffer, inout);
   403                 }
   404         }
   405         return inputmode;
   406 }
   407 
   408 int tb_select_output_mode(int mode)
   409 {
   410         if (mode)
   411                 outputmode = mode;
   412         return outputmode;
   413 }
   414 
   415 void tb_set_clear_attributes(uint32_t fg, uint32_t bg)
   416 {
   417         foreground = fg;
   418         background = bg;
   419 }
   420 
   421 /* -------------------------------------------------------- */
   422 
   423 static int convertnum(uint32_t num, char* buf) {
   424         int i, l = 0;
   425         int ch;
   426         do {
   427                 buf[l++] = '0' + (num % 10);
   428                 num /= 10;
   429         } while (num);
   430         for(i = 0; i < l / 2; i++) {
   431                 ch = buf[i];
   432                 buf[i] = buf[l - 1 - i];
   433                 buf[l - 1 - i] = ch;
   434         }
   435         return l;
   436 }
   437 
   438 #define WRITE_LITERAL(X) bytebuffer_append(&output_buffer, (X), sizeof(X)-1)
   439 #define WRITE_INT(X) bytebuffer_append(&output_buffer, buf, convertnum((X), buf))
   440 
   441 static void write_cursor(int x, int y) {
   442         char buf[32];
   443         WRITE_LITERAL("\033[");
   444         WRITE_INT(y+1);
   445         WRITE_LITERAL(";");
   446         WRITE_INT(x+1);
   447         WRITE_LITERAL("H");
   448 }
   449 
   450 static void write_sgr(uint32_t fg, uint32_t bg) {
   451         char buf[32];
   452 
   453         if (fg & TB_DEFAULT && bg & TB_DEFAULT)
   454                 return;
   455 
   456         switch (outputmode) {
   457         case TB_OUTPUT_256:
   458         case TB_OUTPUT_216:
   459         case TB_OUTPUT_GRAYSCALE:
   460                 WRITE_LITERAL("\033[");
   461                 if (!(fg & TB_DEFAULT)) {
   462                         WRITE_LITERAL("38;5;");
   463                         WRITE_INT(fg);
   464                         if (!(bg & TB_DEFAULT)) {
   465                                 WRITE_LITERAL(";");
   466                         }
   467                 }
   468                 if (!(bg & TB_DEFAULT)) {
   469                         WRITE_LITERAL("48;5;");
   470                         WRITE_INT(bg);
   471                 }
   472                 WRITE_LITERAL("m");
   473                 break;
   474         case TB_OUTPUT_TRUECOLOR:
   475                 WRITE_LITERAL("\033[");
   476                 if (!(fg & TB_DEFAULT)) {
   477                         if (fg & TB_PALETTE) {
   478                                 fg = tb_palette[fg & 0xF];
   479                         }
   480                         WRITE_LITERAL("38;2;");
   481                         WRITE_INT(fg >> 16 & 0xFF); // fg R
   482                         WRITE_LITERAL(";");
   483                         WRITE_INT(fg >> 8 & 0xFF);  // fg G
   484                         WRITE_LITERAL(";");
   485                         WRITE_INT(fg & 0xFF);       // fg B
   486                         if (!(bg & TB_DEFAULT)) {
   487                                 WRITE_LITERAL(";");
   488                         }
   489                 }
   490                 if (!(bg & TB_DEFAULT)) {
   491                         if (bg & TB_PALETTE) {
   492                                 bg = tb_palette[bg & 0xF];
   493                         }
   494                         WRITE_LITERAL("48;2;");
   495                         WRITE_INT(bg >> 16 & 0xFF); // bg R
   496                         WRITE_LITERAL(";");
   497                         WRITE_INT(bg >> 8 & 0xFF);  // bg G
   498                         WRITE_LITERAL(";");
   499                         WRITE_INT(bg & 0xFF);       // bg B
   500                 }
   501                 WRITE_LITERAL("m");
   502                 break;
   503         case TB_OUTPUT_NORMAL:
   504         default:
   505                 // 16 color ISO
   506                 // num   fg         bg
   507                 // 0-7   3(N)m      4(N)m
   508                 // 8-15  1;3(N-8)m  1;4(N-8)m
   509 
   510                 // in bold
   511                 // 0-7   9(N)m      10(N)m
   512                 // 8-15  1;9(N-8)m  1;9(N-8)m
   513                 WRITE_LITERAL("\033[");
   514                 if (!(fg & TB_DEFAULT)) {
   515                         if (fg > 7) { // 8 light colors
   516                                 WRITE_LITERAL("1;3");
   517                                 WRITE_INT(fg - 8);
   518                         }
   519                         else {
   520                                 WRITE_LITERAL("3");
   521                                 WRITE_INT(fg);
   522                         }
   523                         if (!(bg & TB_DEFAULT)) {
   524                                 WRITE_LITERAL(";");
   525                         }
   526                 }
   527                 if (!(bg & TB_DEFAULT)) {
   528                         if (bg > 7 ) { // 8 light colors
   529                                 WRITE_LITERAL("10");
   530                                 WRITE_INT(bg - 8);
   531                         }
   532                         else {
   533                                 WRITE_LITERAL("4");
   534                                 WRITE_INT(bg);
   535                         }
   536                 }
   537                 WRITE_LITERAL("m");
   538                 break;
   539         }
   540 }
   541 
   542 static void cellbuf_init(struct cellbuf *buf, int width, int height)
   543 {
   544         buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height);
   545         assert(buf->cells);
   546         buf->width = width;
   547         buf->height = height;
   548 }
   549 
   550 static void cellbuf_resize(struct cellbuf *buf, int width, int height)
   551 {
   552         if (buf->width == width && buf->height == height)
   553                 return;
   554 
   555         int oldw = buf->width;
   556         int oldh = buf->height;
   557         struct tb_cell *oldcells = buf->cells;
   558 
   559         cellbuf_init(buf, width, height);
   560         cellbuf_clear(buf);
   561 
   562         int minw = (width < oldw) ? width : oldw;
   563         int minh = (height < oldh) ? height : oldh;
   564         int i;
   565 
   566         for (i = 0; i < minh; ++i) {
   567                 struct tb_cell *csrc = oldcells + (i * oldw);
   568                 struct tb_cell *cdst = buf->cells + (i * width);
   569                 memcpy(cdst, csrc, sizeof(struct tb_cell) * minw);
   570         }
   571 
   572         free(oldcells);
   573 }
   574 
   575 static void cellbuf_clear(struct cellbuf *buf)
   576 {
   577         int i;
   578         int ncells = buf->width * buf->height;
   579 
   580         for (i = 0; i < ncells; ++i) {
   581                 buf->cells[i].ch = ' ';
   582                 buf->cells[i].fg = foreground;
   583                 buf->cells[i].bg = background;
   584         }
   585 }
   586 
   587 static void cellbuf_free(struct cellbuf *buf)
   588 {
   589         free(buf->cells);
   590 }
   591 
   592 static void get_term_size(int *w, int *h)
   593 {
   594         struct winsize sz = {0};
   595 
   596         ioctl(inout, TIOCGWINSZ, &sz);
   597 
   598         if (w) *w = sz.ws_col;
   599         if (h) *h = sz.ws_row;
   600 }
   601 
   602 static void update_term_size(void)
   603 {
   604         struct winsize sz = {0};
   605 
   606         ioctl(inout, TIOCGWINSZ, &sz);
   607 
   608         termw = sz.ws_col;
   609         termh = sz.ws_row;
   610 }
   611 
   612 static void send_attr(uint32_t fg, uint32_t bg)
   613 {
   614 #define LAST_ATTR_INIT 0xFFFFFFFF
   615         static uint32_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT;
   616         if (fg != lastfg || bg != lastbg) {
   617                 bytebuffer_puts(&output_buffer, funcs[T_SGR0]);
   618 
   619                 uint32_t fgcol;
   620                 uint32_t bgcol;
   621 
   622                 switch (outputmode) {
   623                 case TB_OUTPUT_256:
   624                         fgcol = fg & (0xFF | TB_DEFAULT);
   625                         bgcol = bg & (0xFF | TB_DEFAULT);
   626                         break;
   627 
   628                 case TB_OUTPUT_216:
   629                         fgcol = fg & 0xFF; if (fgcol > 215) fgcol = 7;
   630                         bgcol = bg & 0xFF; if (bgcol > 215) bgcol = 0;
   631                         fgcol += 0x10 | (fg & TB_DEFAULT);
   632                         bgcol += 0x10 | (bg & TB_DEFAULT);
   633                         break;
   634 
   635                 case TB_OUTPUT_GRAYSCALE:
   636                         fgcol = fg & 0xFF; if (fgcol > 23) fgcol = 23;
   637                         bgcol = bg & 0xFF; if (bgcol > 23) bgcol = 0;
   638                         fgcol += 0xe8 | (fg & TB_DEFAULT);
   639                         bgcol += 0xe8 | (bg & TB_DEFAULT);
   640                         break;
   641 
   642                 case TB_OUTPUT_TRUECOLOR:
   643                         fgcol = fg;
   644                         bgcol = bg;
   645                         break;
   646                 case TB_OUTPUT_NORMAL:
   647                 default:
   648                         fgcol = fg & (0x0F | TB_DEFAULT);
   649                         bgcol = bg & (0x0F | TB_DEFAULT);
   650                 }
   651 
   652                 if (fg & TB_BOLD)
   653                         bytebuffer_puts(&output_buffer, funcs[T_BOLD]);
   654                 if (bg & TB_BLINK)
   655                         bytebuffer_puts(&output_buffer, funcs[T_BLINK]);
   656                 if (fg & TB_UNDERLINE)
   657                         bytebuffer_puts(&output_buffer, funcs[T_UNDERLINE]);
   658                 if (fg & TB_REVERSE)
   659                         bytebuffer_puts(&output_buffer, funcs[T_REVERSE]);
   660                 if (fg & TB_FAINT)
   661                         bytebuffer_puts(&output_buffer, "\x1B[2m");
   662                 if (fg & TB_ITALIC)
   663                         bytebuffer_puts(&output_buffer, "\x1B[3m");
   664                 if (bg & TB_HIDDEN)
   665                         bytebuffer_puts(&output_buffer, "\x1B[8m");
   666                 if (bg & TB_CROSSED)
   667                         bytebuffer_puts(&output_buffer, "\x1B[9m");
   668 
   669                 write_sgr(fgcol, bgcol);
   670 
   671                 lastfg = fg;
   672                 lastbg = bg;
   673         }
   674 }
   675 
   676 static void send_char(int x, int y, uint32_t c)
   677 {
   678         char buf[7];
   679         int bw = tb_utf8_unicode_to_char(buf, c);
   680         if (x-1 != lastx || y != lasty)
   681                 write_cursor(x, y);
   682         lastx = x; lasty = y;
   683         if(!c) buf[0] = ' '; // replace 0 with whitespace
   684         bytebuffer_append(&output_buffer, buf, bw);
   685 }
   686 
   687 static void send_clear(void)
   688 {
   689         send_attr(foreground, background);
   690         bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]);
   691         if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
   692                 write_cursor(cursor_x, cursor_y);
   693         bytebuffer_flush(&output_buffer, inout);
   694 
   695         /* we need to invalidate cursor position too and these two vars are
   696          * used only for simple cursor positioning optimization, cursor
   697          * actually may be in the correct place, but we simply discard
   698          * optimization once and it gives us simple solution for the case when
   699          * cursor moved */
   700         lastx = LAST_COORD_INIT;
   701         lasty = LAST_COORD_INIT;
   702 }
   703 
   704 static void sigwinch_handler(int xxx)
   705 {
   706         (void) xxx;
   707         const int zzz = 1;
   708         write(winch_fds[1], &zzz, sizeof(int));
   709 }
   710 
   711 static void update_size(void)
   712 {
   713         update_term_size();
   714         cellbuf_resize(&back_buffer, termw, termh);
   715         cellbuf_resize(&front_buffer, termw, termh);
   716         cellbuf_clear(&front_buffer);
   717         send_clear();
   718 }
   719 
   720 static int read_up_to(int n) {
   721         assert(n > 0);
   722         const int prevlen = input_buffer.len;
   723         bytebuffer_resize(&input_buffer, prevlen + n);
   724 
   725         int read_n = 0;
   726         while (read_n <= n) {
   727                 ssize_t r = 0;
   728                 if (read_n < n) {
   729                         r = read(inout, input_buffer.buf + prevlen + read_n, n - read_n);
   730                 }
   731 #ifdef __CYGWIN__
   732                 // While linux man for tty says when VMIN == 0 && VTIME == 0, read
   733                 // should return 0 when there is nothing to read, cygwin's read returns
   734                 // -1. Not sure why and if it's correct to ignore it, but let's pretend
   735                 // it's zero.
   736                 if (r < 0) r = 0;
   737 #endif
   738                 if (r < 0) {
   739                         // EAGAIN / EWOULDBLOCK shouldn't occur here
   740                         assert(errno != EAGAIN && errno != EWOULDBLOCK);
   741                         return -1;
   742                 } else if (r > 0) {
   743                         read_n += r;
   744                 } else {
   745                         bytebuffer_resize(&input_buffer, prevlen + read_n);
   746                         return read_n;
   747                 }
   748         }
   749         assert(!"unreachable");
   750         return 0;
   751 }
   752 
   753 static int wait_fill_event(struct tb_event *event, struct timeval *timeout, int sock)
   754 {
   755         // ;-)
   756 #define ENOUGH_DATA_FOR_PARSING 64
   757         fd_set events = {0};
   758 
   759         // try to extract event from input buffer, return on success
   760         event->type = TB_EVENT_KEY;
   761         if (extract_event(event, &input_buffer, inputmode))
   762                 return event->type;
   763 
   764         // it looks like input buffer is incomplete, let's try the short path,
   765         // but first make sure there is enough space
   766         int n = read_up_to(ENOUGH_DATA_FOR_PARSING);
   767         if (n < 0)
   768                 return -1;
   769         if (n > 0 && extract_event(event, &input_buffer, inputmode))
   770                 return event->type;
   771 
   772         // n == 0, or not enough data, let's go to select
   773         while (1) {
   774                 FD_ZERO(&events);
   775                 FD_SET(inout, &events);
   776                 FD_SET(winch_fds[0], &events);
   777                 if (sock) FD_SET(sock, &events);
   778                 int maxfd = (winch_fds[0] > inout) ? winch_fds[0] : inout;
   779                 maxfd     = maxfd > sock ? maxfd : sock;
   780                 int result = select(maxfd+1, &events, NULL, NULL, timeout);
   781                 if (!result)
   782                         return 0;
   783 
   784                 if (FD_ISSET(inout, &events)) {
   785                         event->type = TB_EVENT_KEY;
   786                         n = read_up_to(ENOUGH_DATA_FOR_PARSING);
   787                         if (n < 0)
   788                                 return -1;
   789 
   790                         if (n == 0)
   791                                 continue;
   792 
   793                         if (extract_event(event, &input_buffer, inputmode))
   794                                 return event->type;
   795                 }
   796                 if (FD_ISSET(winch_fds[0], &events)) {
   797                         event->type = TB_EVENT_RESIZE;
   798                         int zzz = 0;
   799                         read(winch_fds[0], &zzz, sizeof(int));
   800                         buffer_size_change_request = 1;
   801                         get_term_size(&event->w, &event->h);
   802                         return TB_EVENT_RESIZE;
   803                 }
   804                 if (sock && FD_ISSET(sock, &events)) {
   805                         event->type = TB_EVENT_SOCKET;
   806                         return TB_EVENT_SOCKET;
   807                 }
   808         }
   809 }