diff options
Diffstat (limited to 'vui.c')
| -rw-r--r-- | vui.c | 874 |
1 files changed, 874 insertions, 0 deletions
@@ -0,0 +1,874 @@ +#include <sys/ioctl.h> + +#include <stdlib.h> +#include <stdbit.h> +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <assert.h> + +#include <unistd.h> +#include <termios.h> +#include <signal.h> +#include <poll.h> +#include <time.h> + +#include "wrmr.h" +#include "vui.h" + +#define CSI "\x1b[" + +/* terminal control */ + +static void vui_getwinsz(unsigned *w, unsigned *h); +static void vui_clrspan(VuiBuffer *buf, unsigned x0, unsigned x1, unsigned y); +static void vui_clrtoeol(VuiBuffer *buf, unsigned x0, unsigned y); + +static void attr_chg(VuiAttr *ptr, VuiAttr to); +static inline void curs_move(unsigned *restrict ptr_x, unsigned *restrict ptr_y, unsigned dst_x, unsigned dst_y); + +static void on_resized(int _); +static void adjust_win_size(void); +static void resize_win(unsigned nw, unsigned nh); + +/* buffer handling */ + +static void clear_buf(VuiBuffer *buf); +static void resize_buf(VuiBuffer *buf, unsigned nw, unsigned nh); +static void free_buf(VuiBuffer *buf); + +static inline int bchr_equiv(VuiBuffer *a, VuiBuffer *b, unsigned i); + +/* input */ + +static unsigned getk(void); +static unsigned getk_utf8(uint8_t start); +static VuiKey bad_key(void); +static VuiKey meta_key(u32 c); +static VuiKey esc_key(u32 c); + +/* output */ + +static void vui_out_fit(size_t n); +static void vui_outf(const char *fmt, ...); +static inline void vui_outc(char c); +static inline void vui_outvc(VuiChar c); +static inline void vui_outvcn(VuiChar *c, size_t n); +static inline void vui_outsn(const char *s, unsigned n); +static inline void vui_outs(const char *s); +static inline void vui_out_flush(void); + +static inline const char *utf8_next(u32 *out, const char *src); +static void utf8_decode(uint32_t *dst, const char *src, unsigned n); + +/* globals */ + +static struct termios vui_init_stdin_tos, vui_init_stdout_tos, vui_raw_stdin_tos, vui_raw_stdout_tos; + +static char *vui_out = NULL; +static size_t vui_outn = 0; +static size_t vui_out_cap = 0; + +static VuiWindow win = { + .front = &win.buf1, + .back = &win.buf2, +}; + +/* TODO + * + * - clean up keyboard input + * - separate out library interface + * - maybe some custom colors + */ + +void vui_redraw_fn(void (*fn)(void *ctx)) { + win.redraw_fn = fn; +} + +void vui_redraw_ctx(void *ctx) { + win.redraw_ctx = ctx; +} + +VuiWindow *vui_win(void) { + return &win; +} + +static void vui_getwinsz(unsigned *w, unsigned *h) { + struct winsize wsz; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsz); + *w = wsz.ws_col; + *h = wsz.ws_row; +} + +static void vui_clrspan(VuiBuffer *buf, unsigned x0, unsigned x1, unsigned y) { + for (unsigned x = x0; x < x1; x++) { + BCHR(buf, x, y) = ' '; + } + for (unsigned x = x0; x < x1; x++) { + BATTR(buf, x, y) = ATTR_DEFAULT; + } +} + +static void vui_clrtoeol(VuiBuffer *buf, unsigned x0, unsigned y) { + vui_clrspan(buf, x0, buf->width, y); +} + +static void clear_buf(VuiBuffer *buf) { + for (unsigned y = 0; y < buf->height; y++) { + vui_clrtoeol(buf, 0, y); + } +} + +void vui_clear(void) { + clear_buf(win.front); +} + +void vui_fill_rect(VuiChar c, VuiAttr a, int x0, int y0, int width, int height) { + if (x0 < 0) { width += x0; x0 = 0; } + if (y0 < 0) { height += y0; y0 = 0; } + if (x0 + width >= (int)COLS) width = (COLS - 1) - x0; + if (y0 + height >= (int)LINES) height = (LINES - 1) - y0; + if (width < 1 || height < 1) return; + for (int y = y0; y < y0 + height; y++) { + for (int x = x0; x < x0 + width; x++) { + CHR(x, y) = c; + ATTR(x, y) = a; + } + } +} + +void vui_fill(VuiChar c, VuiAttr a) { + vui_fill_rect(c, a, 0, 0, COLS, LINES); +} + +static void resize_buf(VuiBuffer *buf, unsigned nw, unsigned nh) { + VuiChar *nchr = calloc(nw * nh, sizeof(*buf->chr)); + uint16_t *nattr = calloc(nw * nh, sizeof(*buf->attr)); + assert(nchr); + assert(nattr); + + unsigned oldw = buf->width; + unsigned oldh = buf->height; + unsigned minw = nw < oldw ? nw : oldw; + unsigned minh = nh < oldh ? nh : oldh; + for (unsigned y = 0; y < minh; y++) { + memcpy(nchr + y*nw, buf->chr + y*oldw, minw * sizeof(*buf->chr)); + memcpy(nattr + y*nw, buf->attr + y*oldw, minw * sizeof(*buf->attr)); + } + + free(buf->chr); + free(buf->attr); + buf->chr = nchr; + buf->attr = nattr; + buf->width = nw; + buf->height = nh; + for (unsigned y = 0; y < minh; y++) vui_clrtoeol(buf, minw, y); + for (unsigned y = minh; y < buf->height; y++) vui_clrtoeol(buf, 0, y); +} + +static void resize_win(unsigned nw, unsigned nh) { + resize_buf(&win.buf1, nw, nh); + resize_buf(&win.buf2, nw, nh); + win.width = nw; + win.height = nh; +} + +static void adjust_win_size(void) { + unsigned w, h; + vui_getwinsz(&w, &h); + if (w != COLS || h != LINES) { + resize_win(w, h); + win.redraw_all = 1; + } +} + +void vui_curs_vis(int vis) { + win.curs_vis = vis; + if (vis) { + fputs(CSI "?25h", stdout); + } else { + fputs(CSI "?25l", stdout); + } +} + +void vui_curs_pos(int x, int y) { + win.curs_x = x; + win.curs_y = y; +} + +static void on_resized(int _) { + adjust_win_size(); + if (win.redraw_fn) { + win.redraw_fn(win.redraw_ctx); + } + vui_blit(); +} + +void vui_init(void) { + tcgetattr(STDIN_FILENO, &vui_init_stdin_tos); + vui_raw_stdin_tos = vui_init_stdin_tos; + /* + vui_raw_stdin_tos.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG); + vui_raw_stdin_tos.c_lflag |= IGNBRK; + */ + + vui_raw_stdin_tos.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); + vui_raw_stdin_tos.c_oflag &= ~OPOST; + vui_raw_stdin_tos.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + vui_raw_stdin_tos.c_cflag &= ~(CSIZE | PARENB); + vui_raw_stdin_tos.c_cflag |= CS8; + + tcsetattr(STDIN_FILENO, TCSANOW, &vui_raw_stdin_tos); + + tcgetattr(STDOUT_FILENO, &vui_init_stdout_tos); + vui_raw_stdout_tos = vui_init_stdout_tos; + vui_raw_stdout_tos.c_oflag &= ~OPOST; + tcsetattr(STDOUT_FILENO, TCSANOW, &vui_raw_stdout_tos); + + /* set white:black to default */ + printf(CSI "40;37m" CSI "8]"); + vui_curs_vis(0); + win.redraw_all = 1; + + adjust_win_size(); + signal(SIGWINCH, on_resized); +} + + +static void free_buf(VuiBuffer *buf) { + free(buf->chr); + free(buf->attr); + buf->chr = NULL; + buf->attr = NULL; + buf->width = 0; + buf->height = 0; +} + +static inline int bchr_equiv(VuiBuffer *a, VuiBuffer *b, unsigned i) { + return a->chr[i] == b->chr[i] && a->attr[i] == b->attr[i]; +} + +void vui_fini(void) { + vui_curs_vis(1); + free_buf(&win.buf1); + free_buf(&win.buf2); + free(vui_out); + vui_out = NULL; + vui_out_cap = 0; + printf(CSI "0m" CSI "H" CSI "2J"); + tcsetattr(STDIN_FILENO, TCSANOW, &vui_init_stdin_tos); + tcsetattr(STDOUT_FILENO, TCSANOW, &vui_init_stdout_tos); +} + +static void vui_out_fit(size_t n) { + size_t c = stdc_bit_ceil(n); + if (c > vui_out_cap) { + char *p = realloc(vui_out, c); + if (!p) { + vui_fini(); + fprintf(stderr, "failed to reallocate vui output buffer\n"); + exit(1); + } + vui_out = p; + vui_out_cap = c; + } +} + +static void vui_outf(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + va_start(ap, fmt); + vui_out_fit(vui_outn + n); + vsprintf(&vui_out[vui_outn], fmt, ap); + vui_outn += n; + va_end(ap); +} + + +/* lookup table from utf-8 codepoint to byte length: + * { 6,6,6,6,6,6,5,5,5,5,5,4,4,4,4,4,3,3,3,3,3,2,2,2,2,1,1,1,1,1,1,1,1, } + * len(cp) = tbl[32 - clz(cp)]. + * + * how could we possibly reduce this? + * 6 sixes, 5 fives, 5 fours, 5 threes, four twos, and eight ones. + * no obvious pattern. + * + * to avoid the subtraction we reverse: + * { 1,1,1,1,1,1,1,1,2,2,2,2,3,3,3,3,3,4,4,4,4,4,5,5,5,5,5,6,6,6,6,6,6, } + * + * subtract one from each: + * { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,2,3,3,3,3,3,4,4,4,4,4,5,5,5,5,5,5, } + * + * clamp to <=3 (no unicode codepoints are actually that long): + * { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, } + * + * concatenate bits: + * 0b0000000000000001010101101010101011111111111111111111111111111111 + * + * convert to hex, and now: + * len(cp) = 1 + (0x156AAFFFFFFFF >> (2 * clz(cp))) & 3 + */ + +#define UTF8_CP_LEN_BITS ((uint64_t)0x156AAFFFFFFFF) +#define UTF8_CP_SHIFT(cp) (stdc_leading_zeros((uint32_t)cp) << 1) +#define UTF8_CP_LEN(cp) (1 + ((UTF8_CP_LEN_BITS >> UTF8_CP_SHIFT(cp)) & 3)) + +static inline void vui_outc(char c) { + vui_out_fit(vui_outn + 1); + vui_out[vui_outn++] = c; +} + +/* don't pass a NUL to this */ +/* it doesn't make sense to do so, and assuming non-zero lets us dodge a branch + * in stdc_leading_zeros() */ +static inline void vui_outvc(VuiChar c) { + ASSUME(c > 0 && c <= 0x110000); + uint8_t len = UTF8_CP_LEN(c); + vui_out_fit(vui_outn + len); + ASSUME(len > 0 && len < 5); + + if (len == 1) { + vui_out[vui_outn++] = c; + return; + } + + for (unsigned i = len; --i;) { + vui_out[vui_outn + i] = 0x80 | (c & 0x3f); + c >>= 6; + } + + vui_out[vui_outn] = (0xf0 << (4 - len)) | c; + vui_outn += len; +} + +static inline void vui_outvcn(VuiChar *c, size_t n) { + while (n--) vui_outvc(*c++); +} + +static inline void vui_outsn(const char *s, unsigned n) { + vui_out_fit(vui_outn + n); + memcpy(&vui_out[vui_outn], s, n); + vui_outn += n; +} + +static inline void vui_outs(const char *s) { + vui_outsn(s, strlen(s)); +} + +static inline void vui_out_flush(void) { + fwrite(vui_out, 1, vui_outn, stdout); + fflush(stdout); +#if 0 + static unsigned out_frame = 0; + FILE *f = fopen("out_log.txt", "a"); + assert(f); + fprintf(f, "\n\n:: OUTPUT FRAME %u ::\n", ++out_frame); + fwrite(vui_out, 1, vui_outn, f); + fclose(f); +#endif + vui_outn = 0; +} + +static inline void curs_move(unsigned *restrict ptr_x, unsigned *restrict ptr_y, unsigned dst_x, unsigned dst_y) { + unsigned src_x = *ptr_x; + unsigned src_y = *ptr_y; + if (src_x != dst_x && src_y != dst_y) { + if (dst_x == src_x - 1) { + vui_outf(CSI "\b%ud", dst_y + 1); + } else if (dst_x > 0) { + vui_outf(CSI "%u;%uH", dst_y + 1, dst_x + 1); + } else if (dst_y > 0) { + vui_outf(CSI "%uH", dst_y + 1); + } else { + vui_outs(CSI "H"); + } + } else if (src_x != dst_x) { + if (dst_x == 0) { + vui_outc('\r'); + } else if (dst_x == src_x - 1) { + vui_outc('\b'); + } else { + vui_outf(CSI "%uG", dst_x + 1); + } + } else if (src_y != dst_y) { + if (dst_y == src_y - 1 && dst_x == src_x + 1) { + vui_outs("\x1b" "M"); + abort(); + } else if (dst_y == src_y + 1 && dst_x == src_x + 1) { + vui_outs("\x1b" "D"); + abort(); + } else if (dst_y > 0) { + vui_outf(CSI "%ud", dst_y + 1); + } else { + vui_outs(CSI "d"); + } + } + *ptr_x = dst_x; + *ptr_y = dst_y; +} + +#define M(s) do {\ + if (sep) vui_outc(';');\ + vui_outs(s);\ + sep = 1;\ +} while(0) + +static void attr_chg(VuiAttr *ptr, VuiAttr to) { + VuiAttr from = *ptr; + if (from == to) return; + + int attr_chg_count = stdc_count_ones(ATTR_A(from) ^ ATTR_A(to)); + int chg_attr = (attr_chg_count != 0); + if (!chg_attr) goto chg_colors; + + /* deduct color changes */ + attr_chg_count -= (ATTR_FG(to) == ATTR_FG(from)) && (ATTR_FG(to) != ATTR_DEFAULT); + attr_chg_count -= (ATTR_BG(to) == ATTR_BG(from)) && (ATTR_BG(to) != ATTR_DEFAULT); + + int should_rebuild = (attr_chg_count > 1) || to == ATTR_DEFAULT; + + if (should_rebuild) { + vui_outs(CSI "0"); + if (to & A_BOLD) vui_outs(";1"); + if (to & A_DIM) vui_outs(";2"); + if (to & A_ITALIC) vui_outs(";3"); + if (to & A_UNDERSCORE) vui_outs(";4"); + if (to & A_BLINK) vui_outs(";5"); + if (to & A_REVERSE) vui_outs(";7"); + from = ATTR_DEFAULT; + assert(ATTR_FG(from) == FG_WHITE); + assert(ATTR_BG(from) == BG_BLACK); + } else { + int sep = 0; + vui_outs(CSI); + if ((to ^ from) & A_BOLD) { + if (to & A_BOLD) M("1"); + else { + M("22"); + from &= (~A_BOLD & ~A_DIM); + } + } + if ((to ^ from) & A_DIM) { + if (to & A_BOLD) M("2"); + else { + M("22"); + from &= (~A_BOLD & ~A_DIM); + } + } + if ((to ^ from) & A_ITALIC) M((to & A_ITALIC) ? "3" : "23"); + if ((to ^ from) & A_UNDERSCORE) M((to & A_UNDERSCORE) ? "4" : "24"); + if ((to ^ from) & A_BLINK) M((to & A_BLINK) ? "5" : "25"); + if ((to ^ from) & A_REVERSE) M((to & A_REVERSE) ? "7" : "27"); + } + +chg_colors:; + + int f_fg = ATTR_FG(from); + int f_bg = ATTR_BG(from); + int t_fg = ATTR_FG(to); + int t_bg = ATTR_BG(to); + + int chg_fg = (t_fg != f_fg); + int chg_bg = (t_bg != f_bg); + if (chg_fg || chg_bg) { + if (chg_attr) vui_outc(';'); + else vui_outs(CSI); + if (chg_fg) { + vui_outc(t_fg > 7 ? '9' : '3'); + vui_outc((t_fg & 7) + '0'); + } + if (chg_bg) { + if (chg_fg) vui_outc(';'); + if (t_bg > 7) { + vui_outs("10"); + } else { + vui_outc('4'); + } + vui_outc((t_bg & 7) + '0'); + } + } + + if (chg_fg || chg_bg || chg_attr) vui_outc('m'); + *ptr = to; +} + +unsigned vui_changes = 0; +unsigned vui_max_change = 0; +unsigned vui_out_chars = 0; +int scrolled_x = 0, scrolled_y = 0; + +void vui_scroll_buf(VuiBuffer *b, int dx, int dy); + +void vui_blit(void) { + VuiBuffer *front = win.front; + VuiBuffer *back = win.back; + + // vui_outf(CSI "H" CSI "0m"); + // vui_outs(CSI "2J"); + // vui_outs(CSI "3J"); + static VuiAttr attr_last = ATTR_DEFAULT; + static unsigned cur_x = 0; + static unsigned cur_y = 0; + + /* + vui_outs(CSI "H" CSI "0m"); + attr_last = ATTR_DEFAULT; + cur_x = 0; + cur_y = 0; + */ + + scrolled_x = win.scroll_x; + scrolled_y = win.scroll_y; + + //win.redraw_all = 1; + if (win.redraw_all) { + vui_outs(CSI "H" CSI "2J" CSI "0m"); + attr_last = ATTR_DEFAULT; + vui_changes = COLS * LINES; + unsigned max = COLS * LINES; + for (unsigned i = 0; i < max; i++) { + attr_chg(&attr_last, front->attr[i]); + vui_outvc(front->chr[i]); + } + vui_outs(CSI "H"); + cur_x = 0; + cur_y = 0; + win.redraw_all = 0; + win.scroll_x = 0; + win.scroll_y = 0; + goto end; + } + + vui_changes = 0; + vui_max_change = 0; + + if (win.scroll_x || win.scroll_y) { + if ((unsigned)abs(win.scroll_x) >= COLS || (unsigned)abs(win.scroll_y) > LINES) { + vui_outs(CSI "2J"); + goto scrolled; + } + + /* spaces inserted by scrolling will have the last-used attribute */ + /* which we don't really want */ + attr_chg(&attr_last, ATTR_DEFAULT); + + if (win.scroll_x < 0) { + /* delete chars at start of line */ + for (unsigned y = 0; y < LINES; y++) { + curs_move(&cur_x, &cur_y, 0, y); + vui_outf(CSI "%dP", -win.scroll_x); + } + } else if (win.scroll_x > 0) { + /* insert spaces at start of line */ + for (unsigned y = 0; y < LINES; y++) { + curs_move(&cur_x, &cur_y, 0, y); + vui_outf(CSI "%d@", win.scroll_x); + } + } + + /* xterm has \x1b[S and \x1b[T to scroll directly */ + /* but these don't work in linux vtty */ + if (win.scroll_y < 0) { + /* delete lines at top */ + curs_move(&cur_x, &cur_y, 0, 0); + if (win.scroll_y == -1) vui_outs(CSI "M"); + else vui_outf(CSI "%dM", -win.scroll_y); + } else if (win.scroll_y > 0) { + /* insert blank lines at top */ + curs_move(&cur_x, &cur_y, 0, 0); + if (win.scroll_y == 1) vui_outs(CSI "L"); + else vui_outf(CSI "%dL", win.scroll_y); + } + +scrolled: + vui_scroll_buf(back, win.scroll_x, win.scroll_y); + win.scroll_x = 0; + win.scroll_y = 0; + } + + for (unsigned y = 0; y < LINES; y++) { + unsigned x = 0; + while (x < COLS) { + while (x < COLS && bchr_equiv(back, front, (y * COLS) + x)) x++; + if (x >= COLS) break; + unsigned x0 = x; + VuiAttr a = ATTR(x0, y); + while (x < COLS && !bchr_equiv(back, front, (y * COLS) + x) && BATTR(front, x, y) == a) x++; + if (x0 != x) { + vui_changes++; + curs_move(&cur_x, &cur_y, x0, y); + attr_chg(&attr_last, a); + vui_outvcn(&CHR(x0, y), x - x0); + cur_x = x; + if (x - x0 > vui_max_change) vui_max_change = x - x0; + } + } + } + +end: + if (win.curs_vis) { + curs_move(&cur_x, &cur_y, win.curs_x, win.curs_y); + } + + vui_out_chars = vui_outn; + vui_out_flush(); + memcpy(back->chr, front->chr, sizeof(*back->chr) * (back->width * back->height)); + memcpy(back->attr, front->attr, sizeof(*back->attr) * (back->width * back->height)); + win.front = back; + win.back = front; +} + +void vui_chra(int x, int y, VuiChar c, VuiAttr a) { + if (x >= 0 && x < (int)COLS && y >= 0 && y < (int)LINES) { + CHR(x, y) = c; + ATTR(x, y) = a; + } +} + +void vui_chr(int x, int y, VuiChar c) { + vui_chra(x, y, c, ATTR_DEFAULT); +} + +/* +static inline u32 utf8_next(u32 *p, const char *s, u32 n) { + u32 i = *p; + u8 c = s[i++]; + usize bits = stdc_leading_ones(c); + ASSUME(bits < 5); + u32 cp = c & ((1 << (7-bits)) - 1); + while (bits-- > 1) { + c = s[i++]; + cp = (cp << 6) | (c & 0x3F); + } + *p = i; + return cp; +} +*/ + +static inline const char *utf8_next(u32 *out, const char *src) { + u8 c = *src++; + usize bits = stdc_leading_ones(c); + ASSUME(bits < 5); + u32 cp = c & (-1 >> bits); + while (bits-- > 1) { + c = *src++; + cp = (cp << 6) | (c & 0x3F); + } + *out = cp; + return src; +} + +static void utf8_decode(uint32_t *dst, const char *src, unsigned n) { + const char *end = src + n; + while (src < end) src = utf8_next(dst++, src); +} + +void vui_putsna(int x, int y, const char *s, unsigned n, VuiAttr a) { + if (n > COLS - x) n = COLS - x; + utf8_decode(&CHR(x, y), s, n); + for (uint16_t *pa = &ATTR(x, y); n--;) *pa++ = a; +} + +void vui_putsn(int x, int y, const char *s, unsigned n) { + vui_putsna(x, y, s, n, ATTR_DEFAULT); +} + +void vui_putsa(int x, int y, const char *s, VuiAttr a) { + vui_putsna(x, y, s, strlen(s), a); +} + +void vui_puts(int x, int y, const char *s) { + vui_putsn(x, y, s, strlen(s)); +} + +int vui_avprintf(int x, int y, VuiAttr a, const char *fmt, va_list ap) { + va_list ap2; + va_copy(ap2, ap); + int n = vsnprintf(NULL, 0, fmt, ap); + if (x < 0) x = COLS + x - (n - 1); + if (y < 0) y = LINES + y; + if (x + n > (int)COLS) n = COLS - x; + if (x < (int)COLS && y < (int)LINES && n > 0) { + char buf[n + 1]; + vsnprintf(buf, n + 1, fmt, ap2); + vui_putsna(x, y, buf, n, a); + } + va_end(ap2); + return n; +} + +int vui_aprintf(int x, int y, VuiAttr a, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int r = vui_avprintf(x, y, a, fmt, ap); + va_end(ap); + return r; +} + +int vui_printf(int x, int y, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int r = vui_avprintf(x, y, ATTR_DEFAULT, fmt, ap); + va_end(ap); + return r; +} + +int vui_wait_for_input(int ms) { + return poll(&(struct pollfd) { .fd = STDIN_FILENO, .events = POLLIN }, 1, ms); +} + +int vui_has_input(void) { + return vui_wait_for_input(0); +} + +void vui_scroll_buf(VuiBuffer *b, int dx, int dy) { + if ((unsigned)abs(dx) >= b->width || (unsigned)abs(dy) >= b->height) { + clear_buf(b); + return; + } + if (dy > 0) { + memmove(b->chr + (b->width * dy), b->chr, sizeof(*b->chr) * b->width * (b->height - dy)); + memmove(b->attr + (b->width * dy), b->attr, sizeof(*b->attr) * b->width * (b->height - dy)); + for (int y = 0; y < dy; y++) vui_clrtoeol(b, 0, y); + } else if (dy < 0) { + memmove(b->chr, b->chr + (b->width * -dy), sizeof(*b->chr) * b->width * (b->height + dy)); + memmove(b->attr, b->attr + (b->width * -dy), sizeof(*b->attr) * b->width * (b->height + dy)); + for (int y = b->height - 1; y > (int)b->height + dy - 1; y--) vui_clrtoeol(b, 0, y); + } + if (dx > 0) { + for (unsigned i = 0; i < b->height; i++) { + memmove(b->chr + (i * b->width) + dx, b->chr + (i * b->width), sizeof(*b->chr) * (b->width - dx)); + memmove(b->attr + (i * b->width) + dx, b->attr + (i * b->width), sizeof(*b->attr) * (b->width - dx)); + vui_clrspan(b, 0, dx, i); + } + } else if (dx < 0) { + for (unsigned i = 0; i < b->height; i++) { + memmove(b->chr + (i * b->width), b->chr + (i * b->width) - dx, sizeof(*b->chr) * (b->width + dx)); + memmove(b->attr + (i * b->width), b->attr + (i * b->width) - dx, sizeof(*b->attr) * (b->width + dx)); + vui_clrspan(b, b->width + dx - 1, b->width, i); + } + + } +} + +void vui_scroll(int dx, int dy) { + vui_scroll_buf(win.front, dx, dy); + win.scroll_x += dx; + win.scroll_y += dy; +} + +static unsigned getk(void) { + char c; + int r; + do { + r = read(STDIN_FILENO, &c, 1); + } while (r == -1 && errno == EINTR); + if (r == -1) return KEY_EOF; + return c; +} + +static unsigned getk_utf8(uint8_t start) { + unsigned n = stdc_leading_ones(start); + if (n > 4) return KEY_INVALID; + static const uint8_t cpmask[4] = { 0x7f, 0x3f, 0x1f, 0xf }; + uint32_t ch = start & cpmask[n - 1]; + while (--n) { + uint8_t k = getk(); + if ((k & 0xc0) != 0x80) return KEY_INVALID; + ch = (ch << 6) | (k & 0x3f); + } + return ch; +} + +static VuiKey bad_key(void) { + /* yell */ + putchar('\a'); + fflush(stdout); + return KEY_INVALID; +} + +static VuiKey meta_key(u32 c) { + switch (c) { + case '2': return esc_key(getk()) | KEY_SHIFT_BIT; break; + case '3': return esc_key(getk()) | KEY_ALT_BIT; break; + case '4': return esc_key(getk()) | KEY_ALT_BIT | KEY_SHIFT_BIT; break; + case '5': return esc_key(getk()) | KEY_CTRL_BIT; break; + case '6': return esc_key(getk()) | KEY_CTRL_BIT | KEY_SHIFT_BIT; break; + case '7': return esc_key(getk()) | KEY_CTRL_BIT | KEY_ALT_BIT; break; + case '8': return esc_key(getk()) | KEY_CTRL_BIT | KEY_SHIFT_BIT | KEY_ALT_BIT; break; + case '9': return esc_key(getk()) | KEY_META_BIT; break; + case '1': + switch (getk()) { + case '0': return esc_key(getk()) | KEY_META_BIT | KEY_SHIFT_BIT; break; + case '1': return esc_key(getk()) | KEY_META_BIT | KEY_ALT_BIT; break; + case '3': return esc_key(getk()) | KEY_META_BIT | KEY_CTRL_BIT; break; + case '4': return esc_key(getk()) | KEY_META_BIT | KEY_SHIFT_BIT | KEY_CTRL_BIT; break; + case '5': return esc_key(getk()) | KEY_META_BIT | KEY_ALT_BIT | KEY_CTRL_BIT; break; + case '6': return esc_key(getk()) | KEY_META_BIT | KEY_SHIFT_BIT | KEY_ALT_BIT | KEY_CTRL_BIT; break; + default: return bad_key(); + } + default: return bad_key(); + } +} + +static VuiKey esc_key(u32 c) { + switch (c) { + case 'P': return KEY_F1; break; + case 'Q': return KEY_F2; break; + case 'R': return KEY_F3; break; + case 'S': return KEY_F4; break; + case '1': + switch (getk()) { + case ';': return meta_key(getk()); + case '5': + default: return bad_key(); + } + break; + case 'A': return KEY_UP; + case 'B': return KEY_DOWN; + case 'C': return KEY_RIGHT; + case 'D': return KEY_LEFT; + case 'H': return KEY_HOME; + case 'F': return KEY_END; + case '5': + if (getk() != '~') return bad_key(); + return KEY_PGUP; + case '6': + if (getk() != '~') return bad_key(); + return KEY_PGDN; + default: return bad_key(); + } +} + +VuiKey vui_key(void) { + int c = getk(); + switch (c) { + case '\n': + return KEY_RET; + case '\b': case 0x7f: + return KEY_BKSP; + case '\x1b': + if (vui_has_input()) { + c = getk(); + switch (c) { + case 'O': + switch (getk()) { + case 'P': return KEY_F1; break; + case 'Q': return KEY_F2; break; + case 'R': return KEY_F3; break; + case 'S': return KEY_F4; break; + default: return bad_key(); break; + } + case '[': return esc_key(getk()); + default: return bad_key(); + } + } else { + return KEY_ESC; + } + default: + return c & 0x80 ? getk_utf8(c) : c; + } +} |
