tui-lib/tui.c

312 lines
5.4 KiB
C

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include "tui.h"
static struct termios orig_termios;
static struct termios raw;
const struct cell empty_cell = {
' ',
TUI_DEFAULT_FG,
TUI_DEFAULT_BG
};
/* global cell buffer */
struct cell_buffer stdscr = {
NULL,
-1,
-1
};
int cursor_visible = 0;
static void
die(const char *s)
{
perror(s);
exit(EXIT_FAILURE);
}
static void
disable_raw_mode(void)
{
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1)
die("tcsetattr");
}
static void
enable_raw_mode(void)
{
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
die("tcgetattr");
raw = orig_termios;
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
raw.c_cc[VMIN] = 0;
raw.c_cc[VTIME] = 1;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
die("tcsetattr");
}
static void
get_window_size(int *height, int *width)
{
struct winsize ws;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
die("ioctl");
if (height != NULL)
*height = ws.ws_row;
if (width != NULL)
*width = ws.ws_col;
}
static void
init_cell_buffer(struct cell_buffer *cb)
{
long ncells = cb->width * cb->height;
assert(ncells > 0);
cb->cells = malloc(ncells * sizeof(*cb->cells));
for (long i = 0; i < ncells; i++)
cb->cells[i] = empty_cell;
}
static void
free_cell_buffer(struct cell_buffer *cb)
{
free(cb->cells);
}
static void
save_cursor(void)
{
write(STDOUT_FILENO, "\033[s", 3);
}
static void
restore_cursor(void)
{
write(STDOUT_FILENO, "\033[u", 3);
}
/****************************************************/
void
tui_init(void)
{
enable_raw_mode();
tui_set_cursor(0, 0);
get_window_size(&stdscr.height, &stdscr.width);
init_cell_buffer(&stdscr);
tui_clear(&stdscr, empty_cell);
}
void
tui_shutdown(void)
{
free_cell_buffer(&stdscr);
disable_raw_mode();
tui_set_cursor(0, 0);
tui_clear_screen();
if (!cursor_visible)
tui_show_cursor();
}
int
tui_width(void)
{
int width;
get_window_size(NULL, &width);
return width;
}
int
tui_height(void)
{
int height;
get_window_size(&height, NULL);
return height;
}
void
tui_refresh(struct cell_buffer cb)
{
if (cursor_visible)
tui_hide_cursor();
save_cursor();
write(STDOUT_FILENO, "\033[0;0H", 6);
for (int y = 0; y < cb.height; y++) {
for (int x = 0; x < cb.width; x++) {
// currently doesn't handle cell's fg and bg
/* printf("\033[%d;%dm", */
/* cb->cells[current_cell].fg + 29, */
/* cb->cells[current_cell].bg + 39); */
long current_cell = x + y * cb.width;
write(STDOUT_FILENO, &cb.cells[current_cell].ch, 1);
}
if (y != cb.height - 1)
write(STDOUT_FILENO, "\r\n", 2);
}
restore_cursor();
if (cursor_visible)
tui_show_cursor();
fflush(stdout);
}
void
tui_refresh_cell(struct cell_buffer cb, int x, int y)
{
tui_hide_cursor();
save_cursor();
tui_set_cursor(x, y);
write(STDOUT_FILENO, &cb.cells[x + y * cb.width].ch, 1);
restore_cursor();
tui_show_cursor();
}
void
tui_clear(struct cell_buffer *cb, struct cell clear_cell)
{
long ncells = cb->width * cb->height;
for (long i = 0; i < ncells; i++) {
cb->cells[i] = clear_cell;
}
}
void
tui_clear_screen(void)
{
write(STDOUT_FILENO, "\033[2J", 4);
}
void
tui_set_cell(struct cell_buffer *cb, int x, int y, struct cell c)
{
if (x < 0 || y < 0 || x > cb->width || y > cb->height)
return;
cb->cells[x + y * cb->width] = c;
}
/* a bit hacky, might want to improve it a bit */
void
tui_print(struct cell_buffer *cb, int x, int y, const char *s)
{
for (size_t i = x; i < strlen(s); i++) {
tui_set_cell(cb, i, y, (struct cell) {s[i], TUI_DEFAULT_FG, TUI_DEFAULT_BG});
}
}
void
tui_hide_cursor(void)
{
cursor_visible = 0;
write(STDOUT_FILENO, "\033[?25l", 6);
}
void
tui_show_cursor(void)
{
cursor_visible = 1;
write(STDOUT_FILENO, "\033[?25h", 6);
}
void
tui_set_cursor(int x, int y)
{
/* It looks like x and y has to be 1-indexed, so correct this by adding 1 to
* each of them. */
char buffer[80] = {0};
sprintf(buffer, "\033[%d;%dH", y+1, x+1);
write(STDOUT_FILENO, buffer, strlen(buffer));
}
/* can probably be simplified, but it works well */
struct event
tui_poll(void)
{
char c;
/* change attributes to make the program wait for input */
raw.c_cc[VMIN] = 1;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
die("tcsetattr");
read(STDIN_FILENO, &c, 1);
if (c == TUI_KEY_ESC) {
read(STDIN_FILENO, &c, 1);
/* reset attributes */
raw.c_cc[VMIN] = 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
die("tcsetattr");
return (struct event) {
TUI_KEY_ESC, c
};
}
else {
/* reset attributes */
raw.c_cc[VMIN] = 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
die("tcsetattr");
return (struct event) {
0, c
};
}
}
struct event
tui_poll_noprefix(void)
{
char c;
/* change attributes to make the program wait for input */
raw.c_cc[VMIN] = 1;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
die("tcsetattr");
read(STDIN_FILENO, &c, 1);
/* reset attributes */
raw.c_cc[VMIN] = 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
die("tcsetattr");
return (struct event) {
0, c
};
}
struct event
tui_peek(void)
{
char c;
read(STDIN_FILENO, &c, 1);
if (c == TUI_KEY_ESC) {
read(STDIN_FILENO, &c, 1);
return (struct event) {
TUI_KEY_ESC, c
};
}
else {
return (struct event) {
0, c
};
}
}