#include "tim.h" #ifdef TIM_UNIX i64 tim_time_usec(void) { struct timespec ts = {0}; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec * 1000000 + ts.tv_nsec / 1000; } void tim_write_str(cstr s, i32 size) { ssize_t _ = write(STDOUT_FILENO, s, size); (void)_; // remove unused-result warning } void signal_handler(i32 signal) { // signals are written into a fifo pipe and read by event loop ssize_t _ = write(tim.signal_pipe[1], &signal, sizeof(signal)); (void)_; // remove unused-result warning } void tim_update_screen_size(void) { struct winsize ws = {0}; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != 0) { printf("ERROR: can't get console buffer size\n"); exit(1); } i32 w = ws.ws_col; i32 h = ws.ws_row; tim.resized = (u32)(w * h) <= TIM_MAX_CELLS && (w != tim.w || h != tim.h); if (tim.resized) { tim.w = tim.scopes[0].w = w; tim.h = tim.scopes[0].h = h; } } void tim_init_terminal(void) { tcgetattr(STDOUT_FILENO, &tim.attr); // store attributes struct termios attr = tim.attr; // cfmakeraw(&attr); // configure raw mode tcsetattr(STDOUT_FILENO, TCSADRAIN, &attr); // set new attributes tim_write_str(S("\33[?2004l")); // reset bracketed paste mode tim_write_str(S("\33[?1049h")); // use alternate buffer tim_write_str(S("\33[?25l")); // hide cursor tim_write_str(S("\33[?1000h")); // enable mouse tim_write_str(S("\33[?1002h")); // enable button events tim_write_str(S("\33[?1006h")); // use mouse sgr protocol tim_update_screen_size(); // get terminal size i32 err = pipe(tim.signal_pipe); // create signal pipe if (!err) { // signal(SIGWINCH, signal_handler); // terminal size changed } } void tim_reset_terminal(void) { tcsetattr(STDOUT_FILENO, TCSADRAIN, &tim.attr); // restore attributes tim_write_str(S("\33[?1000l")); // disable mouse tim_write_str(S("\33[?1002l")); // disable mouse tim_write_str(S("\33[m")); // reset colors tim_write_str(S("\33[?25h")); // show cursor tim_write_str(S("\33[?1049l")); // exit alternate buffer } // parse input stored in e->s bool parse_input(event* restrict e, i32 n) { char* s = e->s; if (n == 1 || s[0] != 27) { // regular key press e->type = TimEvent_Key; e->key = s[0] == 127 ? TimKey_Backspace : tim_utf8_to_i32(s); return true; } if (n >= 9 && !memcmp(s, S("\33[<"))) { // sgr mouse sequence e->type = TimEvent_Mouse; i32 btn = strtol(s + 3, &s, 10); e->x = strtol(s + 1, &s, 10) - 1; e->y = strtol(s + 1, &s, 10) - 1; if (btn == 0 && s[0] == 'M') { // left button pressed e->key = TimKey_MouseButtonLeft; return true; } return false; } struct {char s[4]; i32 k;} key_table[] = { {"[A" , TimKey_Up}, // {"[B" , TimKey_Down}, // {"[C" , TimKey_Right}, // {"[D" , TimKey_Left}, // {"[2~", TimKey_Insert}, // {"[4h", TimKey_Insert}, // st {"[3~", TimKey_Delete}, // {"[P" , TimKey_Delete}, // st {"[H" , TimKey_Home}, // {"[1~", TimKey_Home}, // rxvt, lxterm, putty, tmux, screen {"[7~", TimKey_Home}, // rxvt {"[F" , TimKey_End}, // {"[4~", TimKey_End}, // rxvt, lxterm, putty, tmux, screen, st {"[8~", TimKey_End}, // rxvt {"[5~", TimKey_PageUp}, // {"[6~", TimKey_PageDown}, // }; if ((n == 3 || n == 4) && s[0] == 27) { // key sequence for (i32 i = 0; i < (i32)ARRAY_SIZE(key_table); i++) { if (!memcmp(s + 1, key_table[i].s, n - 1)) { e->type = TimEvent_Key; e->key = key_table[i].k; return true; } } } return false; } void tim_read_event(i32 timeout_ms) { event* e = &tim.event; struct pollfd pfd[2] = { {.fd = tim.signal_pipe[0], .events = POLLIN}, {.fd = STDIN_FILENO, .events = POLLIN}, }; while (true) { memset(e, 0, sizeof(*e)); i32 r = poll(pfd, 2, timeout_ms > 0 ? timeout_ms : -1); if (r < 0) { // poll error, EINTR or EAGAIN continue; } else if (r == 0) { // poll timeout e->type = TimEvent_Draw; return; } if (pfd[0].revents & POLLIN) { // received signal i32 sig = 0; i32 n = read(tim.signal_pipe[0], &sig, sizeof(sig)); if (n > 0 && sig == SIGWINCH) { // screen size changed e->type = TimEvent_Draw; tim_update_screen_size(); return; } } if (pfd[1].revents & POLLIN) { // received input i32 n = read(STDIN_FILENO, e->s, sizeof(e->s) - 1); if (parse_input(e, n)) { return; } } } // while } #endif // TIM_UNIX