#include #include #include #include #include #include #include #include #include #include #include #include #include #include /* hack: we can't define _XOPEN_SOURCE because that causes OpenBSD to not * include SIGWINCH. But then this prototype is not included on Linux, * triggering a warning. */ extern int wcwidth (wchar_t); #include "termbox.h" #include "bytebuffer.inl" #include "output.inl" #include "input.inl" #define LAST_COORD_INIT -1 static struct termios orig_tios; static struct bytebuffer output_buffer; static struct bytebuffer input_buffer; static int termw = -1; static int termh = -1; static int inout; static int winch_fds[2]; static int cursor_x = 0; static int cursor_y = 0; static uint16_t background = TB_BLACK; static uint16_t foreground = TB_WHITE; static void update_size(void); static void update_term_size(void); static void send_attr(uint16_t fg, uint16_t bg); static void send_clear(void); static void sigwinch_handler(int xxx); static int wait_fill_event(struct tb_event *event, struct timeval *timeout); /* may happen in a different thread */ static volatile int buffer_size_change_request; /* -------------------------------------------------------- */ int tb_init(void) { inout = open("/dev/tty", O_RDWR); if (inout == -1) { return TB_EFAILED_TO_OPEN_TTY; } if (init_term() < 0) { close(inout); return TB_EUNSUPPORTED_TERMINAL; } if (pipe(winch_fds) < 0) { close(inout); return TB_EPIPE_TRAP_ERROR; } struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sigwinch_handler; sa.sa_flags = 0; sigaction(SIGWINCH, &sa, 0); tcgetattr(inout, &orig_tios); struct termios tios; memcpy(&tios, &orig_tios, sizeof(tios)); tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); tios.c_oflag &= ~OPOST; tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); tios.c_cflag &= ~(CSIZE | PARENB); tios.c_cflag |= CS8; tios.c_cc[VMIN] = 0; tios.c_cc[VTIME] = 0; tcsetattr(inout, TCSAFLUSH, &tios); bytebuffer_init(&input_buffer, 128); bytebuffer_init(&output_buffer, 32 * 1024); bytebuffer_puts(&output_buffer, funcs[T_ENTER_KEYPAD]); bytebuffer_puts(&output_buffer, funcs[T_ENTER_MOUSE]); bytebuffer_puts(&output_buffer, funcs[T_ENTER_BRACKETED_PASTE]); bytebuffer_flush(&output_buffer, inout); update_term_size(); return 0; } void tb_shutdown(void) { if (termw == -1) return; bytebuffer_puts(&output_buffer, funcs[T_SGR0]); bytebuffer_puts(&output_buffer, funcs[T_EXIT_KEYPAD]); bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]); bytebuffer_puts(&output_buffer, funcs[T_EXIT_BRACKETED_PASTE]); bytebuffer_flush(&output_buffer, inout); tcsetattr(inout, TCSAFLUSH, &orig_tios); shutdown_term(); close(inout); close(winch_fds[0]); close(winch_fds[1]); bytebuffer_free(&output_buffer); bytebuffer_free(&input_buffer); termw = termh = -1; } int tb_is_active(void) { return termw != -1; } void tb_print(uint32_t ch, uint16_t fg, uint16_t bg) { assert(termw != -1); send_attr(fg, bg); if (ch == 0) { // replace 0 with whitespace bytebuffer_puts(&output_buffer, " "); } else { char buf[7]; int bw = tb_utf8_unicode_to_char(buf, ch); buf[bw] = '\0'; bytebuffer_puts(&output_buffer, buf); } bytebuffer_flush(&output_buffer, inout); } int tb_poll_event(struct tb_event *event) { assert(termw != -1); return wait_fill_event(event, 0); } int tb_peek_event(struct tb_event *event, int timeout) { struct timeval tv; tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000; assert(termw != -1); return wait_fill_event(event, &tv); } int tb_width(void) { assert(termw != -1); return termw; } int tb_height(void) { assert(termw != -1); return termh; } void tb_clear(void) { assert(termw != -1); if (buffer_size_change_request) { update_size(); buffer_size_change_request = 0; } send_clear(); } void tb_set_clear_attributes(uint16_t fg, uint16_t bg) { assert(termw != -1); foreground = fg; background = bg; } /* -------------------------------------------------------- */ static int convertnum(uint32_t num, char* buf) { int i, l = 0; int ch; do { buf[l++] = '0' + (num % 10); num /= 10; } while (num); for(i = 0; i < l / 2; i++) { ch = buf[i]; buf[i] = buf[l - 1 - i]; buf[l - 1 - i] = ch; } return l; } #define WRITE_LITERAL(X) bytebuffer_append(&output_buffer, (X), sizeof(X)-1) #define WRITE_INT(X) bytebuffer_append(&output_buffer, buf, convertnum((X), buf)) void tb_set_cursor(int x, int y) { char buf[32]; WRITE_LITERAL("\033["); WRITE_INT(y+1); WRITE_LITERAL(";"); WRITE_INT(x+1); WRITE_LITERAL("H"); bytebuffer_flush(&output_buffer, inout); } static void get_term_size(int *w, int *h) { struct winsize sz; memset(&sz, 0, sizeof(sz)); ioctl(inout, TIOCGWINSZ, &sz); if (w) *w = sz.ws_col; if (h) *h = sz.ws_row; } static void update_term_size(void) { struct winsize sz; memset(&sz, 0, sizeof(sz)); ioctl(inout, TIOCGWINSZ, &sz); termw = sz.ws_col; termh = sz.ws_row; } static void send_attr(uint16_t fg, uint16_t bg) { #define LAST_ATTR_INIT 0xFFFF static uint16_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT; if (fg != lastfg || bg != lastbg) { bytebuffer_puts(&output_buffer, funcs[T_SGR0]); uint16_t fgcol = fg & 0xFF; uint16_t bgcol = bg & 0xFF; if (fg & TB_BOLD) bytebuffer_puts(&output_buffer, funcs[T_BOLD]); if (bg & TB_BOLD) bytebuffer_puts(&output_buffer, funcs[T_BLINK]); if (fg & TB_UNDERLINE) bytebuffer_puts(&output_buffer, funcs[T_UNDERLINE]); if ((fg & TB_REVERSE) || (bg & TB_REVERSE)) bytebuffer_puts(&output_buffer, funcs[T_REVERSE]); char buf[32]; WRITE_LITERAL("\033[38;5;"); WRITE_INT(fgcol);