vis

a vi-like editor based on Plan 9's structural regular expressions

git clone https://9o.is/git/vis.git

ui-terminal.c

(17313B)


      1 #include <unistd.h>
      2 #include <stdlib.h>
      3 #include <string.h>
      4 #include <strings.h>
      5 #include <limits.h>
      6 #include <ctype.h>
      7 #include <locale.h>
      8 #include <poll.h>
      9 #include <sys/ioctl.h>
     10 #include <sys/types.h>
     11 #include <sys/stat.h>
     12 #include <fcntl.h>
     13 #include <termios.h>
     14 #include <errno.h>
     15 
     16 #include "vis.h"
     17 #include "vis-core.h"
     18 #include "text.h"
     19 #include "util.h"
     20 #include "text-util.h"
     21 
     22 #ifndef DEBUG_UI
     23 #define DEBUG_UI 0
     24 #endif
     25 
     26 #if DEBUG_UI
     27 #define debug(...) do { printf(__VA_ARGS__); fflush(stdout); } while (0)
     28 #else
     29 #define debug(...) do { } while (0)
     30 #endif
     31 
     32 #if CONFIG_CURSES
     33 #include "ui-terminal-curses.c"
     34 #else
     35 #include "ui-terminal-vt100.c"
     36 #endif
     37 
     38 /* helper macro for handling UiTerm.cells */
     39 #define CELL_AT_POS(UI, X, Y) (((UI)->cells) + (X) + ((Y) * (UI)->width));
     40 
     41 #define CELL_STYLE_DEFAULT (CellStyle){.fg = CELL_COLOR_DEFAULT, .bg = CELL_COLOR_DEFAULT, .attr = CELL_ATTR_NORMAL}
     42 
     43 static bool is_default_fg(CellColor c) {
     44 	return is_default_color(c);
     45 }
     46 
     47 static bool is_default_bg(CellColor c) {
     48 	return is_default_color(c);
     49 }
     50 
     51 void ui_die(Ui *tui, const char *msg, va_list ap) {
     52 	ui_terminal_free(tui);
     53 	if (tui->termkey)
     54 		termkey_stop(tui->termkey);
     55 	vfprintf(stderr, msg, ap);
     56 	exit(EXIT_FAILURE);
     57 }
     58 
     59 static void ui_die_msg(Ui *ui, const char *msg, ...) {
     60 	va_list ap;
     61 	va_start(ap, msg);
     62 	ui_die(ui, msg, ap);
     63 	va_end(ap);
     64 }
     65 
     66 static void ui_window_resize(Win *win, int width, int height) {
     67 	debug("ui-win-resize[%s]: %dx%d\n", win->file->name ? win->file->name : "noname", width, height);
     68 	bool status = win->options & UI_OPTION_STATUSBAR;
     69 	win->width  = width;
     70 	win->height = height;
     71 	view_resize(&win->view, width - win->sidebar_width, status ? height - 1 : height);
     72 }
     73 
     74 static void ui_window_move(Win *win, int x, int y) {
     75 	debug("ui-win-move[%s]: (%d, %d)\n", win->file->name ? win->file->name : "noname", x, y);
     76 	win->x = x;
     77 	win->y = y;
     78 }
     79 
     80 static bool color_fromstring(Ui *ui, CellColor *color, const char *s)
     81 {
     82 	if (!s)
     83 		return false;
     84 	if (*s == '#' && strlen(s) == 7) {
     85 		const char *cp;
     86 		unsigned char r, g, b;
     87 		for (cp = s + 1; isxdigit((unsigned char)*cp); cp++);
     88 		if (*cp != '\0')
     89 			return false;
     90 		int n = sscanf(s + 1, "%2hhx%2hhx%2hhx", &r, &g, &b);
     91 		if (n != 3)
     92 			return false;
     93 		*color = color_rgb(ui, r, g, b);
     94 		return true;
     95 	} else if ('0' <= *s && *s <= '9') {
     96 		int index = atoi(s);
     97 		if (index <= 0 || index > 255)
     98 			return false;
     99 		*color = color_terminal(ui, index);
    100 		return true;
    101 	}
    102 
    103 	static const struct {
    104 		const char *name;
    105 		CellColor color;
    106 	} color_names[] = {
    107 		{ "black",   CELL_COLOR_BLACK   },
    108 		{ "red",     CELL_COLOR_RED     },
    109 		{ "green",   CELL_COLOR_GREEN   },
    110 		{ "yellow",  CELL_COLOR_YELLOW  },
    111 		{ "blue",    CELL_COLOR_BLUE    },
    112 		{ "magenta", CELL_COLOR_MAGENTA },
    113 		{ "cyan",    CELL_COLOR_CYAN    },
    114 		{ "white",   CELL_COLOR_WHITE   },
    115 		{ "default", CELL_COLOR_DEFAULT },
    116 	};
    117 
    118 	for (size_t i = 0; i < LENGTH(color_names); i++) {
    119 		if (strcasecmp(color_names[i].name, s) == 0) {
    120 			*color = color_names[i].color;
    121 			return true;
    122 		}
    123 	}
    124 
    125 	return false;
    126 }
    127 
    128 void ui_showcursor(bool show) {
    129 	cursor_visible(show);
    130 }
    131 
    132 bool ui_style_define(Win *win, int id, const char *style) {
    133 	Ui *tui = &win->vis->ui;
    134 	if (id >= UI_STYLE_MAX)
    135 		return false;
    136 	if (!style)
    137 		return true;
    138 
    139 	CellStyle cell_style = CELL_STYLE_DEFAULT;
    140 	char *style_copy = strdup(style), *option = style_copy;
    141 	while (option) {
    142 		while (*option == ' ')
    143 			option++;
    144 		char *next = strchr(option, ',');
    145 		if (next)
    146 			*next++ = '\0';
    147 		char *value = strchr(option, ':');
    148 		if (value)
    149 			for (*value++ = '\0'; *value == ' '; value++);
    150 		if (!strcasecmp(option, "reverse")) {
    151 			cell_style.attr |= CELL_ATTR_REVERSE;
    152 		} else if (!strcasecmp(option, "notreverse")) {
    153 			cell_style.attr &= CELL_ATTR_REVERSE;
    154 		} else if (!strcasecmp(option, "bold")) {
    155 			cell_style.attr |= CELL_ATTR_BOLD;
    156 		} else if (!strcasecmp(option, "notbold")) {
    157 			cell_style.attr &= ~CELL_ATTR_BOLD;
    158 		} else if (!strcasecmp(option, "dim")) {
    159 			cell_style.attr |= CELL_ATTR_DIM;
    160 		} else if (!strcasecmp(option, "notdim")) {
    161 			cell_style.attr &= ~CELL_ATTR_DIM;
    162 		} else if (!strcasecmp(option, "italics")) {
    163 			cell_style.attr |= CELL_ATTR_ITALIC;
    164 		} else if (!strcasecmp(option, "notitalics")) {
    165 			cell_style.attr &= ~CELL_ATTR_ITALIC;
    166 		} else if (!strcasecmp(option, "underlined")) {
    167 			cell_style.attr |= CELL_ATTR_UNDERLINE;
    168 		} else if (!strcasecmp(option, "notunderlined")) {
    169 			cell_style.attr &= ~CELL_ATTR_UNDERLINE;
    170 		} else if (!strcasecmp(option, "blink")) {
    171 			cell_style.attr |= CELL_ATTR_BLINK;
    172 		} else if (!strcasecmp(option, "notblink")) {
    173 			cell_style.attr &= ~CELL_ATTR_BLINK;
    174 		} else if (!strcasecmp(option, "fore")) {
    175 			color_fromstring(&win->vis->ui, &cell_style.fg, value);
    176 		} else if (!strcasecmp(option, "back")) {
    177 			color_fromstring(&win->vis->ui, &cell_style.bg, value);
    178 		}
    179 		option = next;
    180 	}
    181 	tui->styles[win->id * UI_STYLE_MAX + id] = cell_style;
    182 	free(style_copy);
    183 	return true;
    184 }
    185 
    186 static void ui_draw_line(Ui *tui, int x, int y, char c, enum UiStyle style_id) {
    187 	if (x < 0 || x >= tui->width || y < 0 || y >= tui->height)
    188 		return;
    189 	CellStyle style = tui->styles[style_id];
    190 	Cell *cells = tui->cells + y * tui->width;
    191 	while (x < tui->width) {
    192 		cells[x].data[0] = c;
    193 		cells[x].data[1] = '\0';
    194 		cells[x].style = style;
    195 		x++;
    196 	}
    197 }
    198 
    199 static void ui_draw_string(Ui *tui, int x, int y, const char *str, int win_id, enum UiStyle style_id) {
    200 	debug("draw-string: [%d][%d]\n", y, x);
    201 	if (x < 0 || x >= tui->width || y < 0 || y >= tui->height)
    202 		return;
    203 
    204 	/* NOTE: the style that style_id refers to may contain unset values; we need to properly
    205 	 * clear the cell first then go through ui_window_style_set to get the correct style */
    206 	CellStyle default_style = tui->styles[UI_STYLE_MAX * win_id + UI_STYLE_DEFAULT];
    207 	// FIXME: does not handle double width characters etc, share code with view.c?
    208 	Cell *cells = tui->cells + y * tui->width;
    209 	const size_t cell_size = sizeof(cells[0].data)-1;
    210 	for (const char *next = str; *str && x < tui->width; str = next) {
    211 		do next++; while (!ISUTF8(*next));
    212 		size_t len = next - str;
    213 		if (!len)
    214 			break;
    215 		len = MIN(len, cell_size);
    216 		strncpy(cells[x].data, str, len);
    217 		cells[x].data[len] = '\0';
    218 		cells[x].style = default_style;
    219 		ui_window_style_set(tui, win_id, cells + x++, style_id, false);
    220 	}
    221 }
    222 
    223 static void ui_window_draw(Win *win) {
    224 	Ui *ui = &win->vis->ui;
    225 	View *view = &win->view;
    226 	const Line *line = win->view.topline;
    227 
    228 	bool status  = win->options & UI_OPTION_STATUSBAR;
    229 	bool nu      = win->options & UI_OPTION_LINE_NUMBERS_ABSOLUTE;
    230 	bool rnu     = win->options & UI_OPTION_LINE_NUMBERS_RELATIVE;
    231 	bool sidebar = nu || rnu;
    232 
    233 	int width = win->width, height = win->height;
    234 	int sidebar_width = sidebar ? snprintf(NULL, 0, "%zd ", line->lineno + height - 2) : 0;
    235 	if (sidebar_width != win->sidebar_width) {
    236 		view_resize(view, width - sidebar_width, status ? height - 1 : height);
    237 		win->sidebar_width = sidebar_width;
    238 	}
    239 	vis_window_draw(win);
    240 
    241 	Selection *sel = view_selections_primary_get(view);
    242 	size_t prev_lineno = 0, cursor_lineno = sel->line->lineno;
    243 	char buf[(sizeof(size_t) * CHAR_BIT + 2) / 3 + 1 + 1];
    244 	int x = win->x, y = win->y;
    245 	int view_width = view->width;
    246 	Cell *cells = ui->cells + y * ui->width;
    247 	if (x + sidebar_width + view_width > ui->width)
    248 		view_width = ui->width - x - sidebar_width;
    249 	for (const Line *l = line; l; l = l->next, y++) {
    250 		if (sidebar) {
    251 			if (!l->lineno || !l->len || l->lineno == prev_lineno) {
    252 				memset(buf, ' ', sizeof(buf));
    253 				buf[sidebar_width] = '\0';
    254 			} else {
    255 				size_t number = l->lineno;
    256 				if (rnu) {
    257 					number = (win->options & UI_OPTION_LARGE_FILE) ? 0 : l->lineno;
    258 					if (l->lineno > cursor_lineno)
    259 						number = l->lineno - cursor_lineno;
    260 					else if (l->lineno < cursor_lineno)
    261 						number = cursor_lineno - l->lineno;
    262 				}
    263 				snprintf(buf, sizeof buf, "%*zu ", sidebar_width-1, number);
    264 			}
    265 			ui_draw_string(ui, x, y, buf, win->id,
    266 				       (l->lineno == cursor_lineno) ? UI_STYLE_LINENUMBER_CURSOR :
    267 				                                      UI_STYLE_LINENUMBER);
    268 			prev_lineno = l->lineno;
    269 		}
    270 		debug("draw-window: [%d][%d] ... cells[%d][%d]\n", y, x+sidebar_width, y, view_width);
    271 		memcpy(cells + x + sidebar_width, l->cells, sizeof(Cell) * view_width);
    272 		cells += ui->width;
    273 	}
    274 }
    275 
    276 void ui_window_style_set(Ui *tui, int win_id, Cell *cell, enum UiStyle id, bool keep_non_default) {
    277 	CellStyle set = tui->styles[win_id * UI_STYLE_MAX + id];
    278 
    279 	if (id != UI_STYLE_DEFAULT) {
    280 		if (keep_non_default) {
    281 			CellStyle default_style = tui->styles[win_id * UI_STYLE_MAX + UI_STYLE_DEFAULT];
    282 			if (!cell_color_equal(cell->style.fg, default_style.fg))
    283 				set.fg = cell->style.fg;
    284 			if (!cell_color_equal(cell->style.bg, default_style.bg))
    285 				set.bg = cell->style.bg;
    286 		}
    287 		set.fg = is_default_fg(set.fg)? cell->style.fg : set.fg;
    288 		set.bg = is_default_bg(set.bg)? cell->style.bg : set.bg;
    289 		set.attr = cell->style.attr | set.attr;
    290 	}
    291 
    292 	cell->style = set;
    293 }
    294 
    295 bool ui_window_style_set_pos(Win *win, int x, int y, enum UiStyle id, bool keep_non_default) {
    296 	Ui *tui = &win->vis->ui;
    297 	if (x < 0 || y < 0 || y >= win->height || x >= win->width) {
    298 		return false;
    299 	}
    300 	Cell *cell = CELL_AT_POS(tui, win->x + x, win->y + y)
    301 	ui_window_style_set(tui, win->id, cell, id, keep_non_default);
    302 	return true;
    303 }
    304 
    305 void ui_window_status(Win *win, const char *status) {
    306 	if (!(win->options & UI_OPTION_STATUSBAR))
    307 		return;
    308 	Ui *ui = &win->vis->ui;
    309 	enum UiStyle style = ui->selwin == win ? UI_STYLE_STATUS_FOCUSED : UI_STYLE_STATUS;
    310 	ui_draw_string(ui, win->x, win->y + win->height - 1, status, win->id, style);
    311 }
    312 
    313 void ui_arrange(Ui *tui, enum UiLayout layout) {
    314 	debug("ui-arrange\n");
    315 	tui->layout = layout;
    316 	int n = 0, m = !!tui->info[0], x = 0, y = 0;
    317 	for (Win *win = tui->windows; win; win = win->next) {
    318 		if (win->options & UI_OPTION_ONELINE)
    319 			m++;
    320 		else
    321 			n++;
    322 	}
    323 	int max_height = tui->height - m;
    324 	int width = (tui->width / MAX(1, n)) - 1;
    325 	int height = max_height / MAX(1, n);
    326 	for (Win *win = tui->windows; win; win = win->next) {
    327 		if (win->options & UI_OPTION_ONELINE)
    328 			continue;
    329 		n--;
    330 		if (layout == UI_LAYOUT_HORIZONTAL) {
    331 			int h = n ? height : max_height - y;
    332 			ui_window_resize(win, tui->width, h);
    333 			ui_window_move(win, x, y);
    334 			y += h;
    335 		} else {
    336 			int w = n ? width : tui->width - x;
    337 			ui_window_resize(win, w, max_height);
    338 			ui_window_move(win, x, y);
    339 			x += w;
    340 			if (n) {
    341 				Cell *cells = tui->cells;
    342 				for (int i = 0; i < max_height; i++) {
    343 					strcpy(cells[x].data,"│");
    344 					cells[x].style = tui->styles[UI_STYLE_SEPARATOR];
    345 					cells += tui->width;
    346 				}
    347 				x++;
    348 			}
    349 		}
    350 	}
    351 
    352 	if (layout == UI_LAYOUT_VERTICAL)
    353 		y = max_height;
    354 
    355 	for (Win *win = tui->windows; win; win = win->next) {
    356 		if (!(win->options & UI_OPTION_ONELINE))
    357 			continue;
    358 		ui_window_resize(win, tui->width, 1);
    359 		ui_window_move(win, 0, y++);
    360 	}
    361 }
    362 
    363 void ui_draw(Ui *tui) {
    364 	debug("ui-draw\n");
    365 	ui_arrange(tui, tui->layout);
    366 	for (Win *win = tui->windows; win; win = win->next)
    367 		ui_window_draw(win);
    368 	if (tui->info[0])
    369 		ui_draw_string(tui, 0, tui->height-1, tui->info, 0, UI_STYLE_INFO);
    370 	vis_event_emit(tui->vis, VIS_EVENT_UI_DRAW);
    371 	ui_term_backend_blit(tui);
    372 }
    373 
    374 void ui_redraw(Ui *tui) {
    375 	ui_term_backend_clear(tui);
    376 	for (Win *win = tui->windows; win; win = win->next)
    377 		win->view.need_update = true;
    378 }
    379 
    380 void ui_resize(Ui *tui) {
    381 	struct winsize ws;
    382 	int width = 80, height = 24;
    383 
    384 	if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) != -1) {
    385 		if (ws.ws_col > 0)
    386 			width = ws.ws_col;
    387 		if (ws.ws_row > 0)
    388 			height = ws.ws_row;
    389 	}
    390 
    391 	width  = MIN(width,  UI_MAX_WIDTH);
    392 	height = MIN(height, UI_MAX_HEIGHT);
    393 	if (!ui_term_backend_resize(tui, width, height))
    394 		return;
    395 
    396 	size_t size = width*height*sizeof(Cell);
    397 	if (size > tui->cells_size) {
    398 		Cell *cells = realloc(tui->cells, size);
    399 		if (!cells)
    400 			return;
    401 		memset((char*)cells+tui->cells_size, 0, size - tui->cells_size);
    402 		tui->cells_size = size;
    403 		tui->cells = cells;
    404 	}
    405 	tui->width = width;
    406 	tui->height = height;
    407 }
    408 
    409 void ui_window_release(Ui *tui, Win *win) {
    410 	if (!win)
    411 		return;
    412 	if (tui->windows == win)
    413 		tui->windows = win->next;
    414 	if (tui->selwin == win)
    415 		tui->selwin = NULL;
    416 	tui->ids &= ~(1UL << win->id);
    417 }
    418 
    419 void ui_window_focus(Win *new) {
    420 	Win *old = new->vis->ui.selwin;
    421 	if (new->options & UI_OPTION_STATUSBAR)
    422 		new->vis->ui.selwin = new;
    423 	if (old)
    424 		old->view.need_update = true;
    425 	new->view.need_update = true;
    426 }
    427 
    428 void ui_window_options_set(Win *win, enum UiOption options) {
    429 	win->options = options;
    430 	if (options & UI_OPTION_ONELINE) {
    431 		/* move the new window to the end of the list */
    432 		Ui *tui = &win->vis->ui;
    433 		Win *last = tui->windows;
    434 		while (last->next)
    435 			last = last->next;
    436 		if (last != win) {
    437 			if (tui->windows == win)
    438 				tui->windows = win->next;
    439 			last->next = win;
    440 		}
    441 	}
    442 	ui_draw(&win->vis->ui);
    443 }
    444 
    445 void ui_window_swap(Win *a, Win *b) {
    446 	if (a == b || !a || !b)
    447 		return;
    448 	Ui *tui = &a->vis->ui;
    449 	if (tui->windows == a)
    450 		tui->windows = b;
    451 	else if (tui->windows == b)
    452 		tui->windows = a;
    453 	if (tui->selwin == a)
    454 		ui_window_focus(b);
    455 	else if (tui->selwin == b)
    456 		ui_window_focus(a);
    457 }
    458 
    459 bool ui_window_init(Ui *tui, Win *w, enum UiOption options) {
    460 	/* get rightmost zero bit, i.e. highest available id */
    461 	size_t bit = ~tui->ids & (tui->ids + 1);
    462 	size_t id = 0;
    463 	for (size_t tmp = bit; tmp >>= 1; id++);
    464 	if (id >= sizeof(size_t) * 8)
    465 		return NULL;
    466 	size_t styles_size = (id + 1) * UI_STYLE_MAX * sizeof(CellStyle);
    467 	if (styles_size > tui->styles_size) {
    468 		CellStyle *styles = realloc(tui->styles, styles_size);
    469 		if (!styles)
    470 			return NULL;
    471 		tui->styles = styles;
    472 		tui->styles_size = styles_size;
    473 	}
    474 
    475 	tui->ids |= bit;
    476 	w->id = id;
    477 
    478 	CellStyle *styles = &tui->styles[w->id * UI_STYLE_MAX];
    479 	for (int i = 0; i < UI_STYLE_MAX; i++) {
    480 		styles[i] = CELL_STYLE_DEFAULT;
    481 	}
    482 
    483 	styles[UI_STYLE_CURSOR].attr |= CELL_ATTR_REVERSE;
    484 	styles[UI_STYLE_CURSOR_PRIMARY].attr |= CELL_ATTR_REVERSE|CELL_ATTR_BLINK;
    485 	styles[UI_STYLE_SELECTION].attr |= CELL_ATTR_REVERSE;
    486 	styles[UI_STYLE_COLOR_COLUMN].attr |= CELL_ATTR_REVERSE;
    487 	styles[UI_STYLE_STATUS].attr |= CELL_ATTR_REVERSE;
    488 	styles[UI_STYLE_STATUS_FOCUSED].attr |= CELL_ATTR_REVERSE|CELL_ATTR_BOLD;
    489 	styles[UI_STYLE_INFO].attr |= CELL_ATTR_BOLD;
    490 
    491 	if (tui->windows)
    492 		tui->windows->prev = w->prev;
    493 	tui->windows = w;
    494 
    495 	if (text_size(w->file->text) > UI_LARGE_FILE_SIZE) {
    496 		options |= UI_OPTION_LARGE_FILE;
    497 		options &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
    498 	}
    499 
    500 	win_options_set(w, options);
    501 
    502 	return true;
    503 }
    504 
    505 void ui_info_show(Ui *tui, const char *msg, va_list ap) {
    506 	ui_draw_line(tui, 0, tui->height-1, ' ', UI_STYLE_INFO);
    507 	vsnprintf(tui->info, sizeof(tui->info), msg, ap);
    508 }
    509 
    510 void ui_info_hide(Ui *tui) {
    511 	if (tui->info[0])
    512 		tui->info[0] = '\0';
    513 }
    514 
    515 static TermKey *ui_termkey_new(int fd) {
    516 	TermKey *termkey = termkey_new(fd, UI_TERMKEY_FLAGS);
    517 	if (termkey)
    518 		termkey_set_canonflags(termkey, TERMKEY_CANON_DELBS);
    519 	return termkey;
    520 }
    521 
    522 static TermKey *ui_termkey_reopen(Ui *ui, int fd) {
    523 	int tty = open("/dev/tty", O_RDWR);
    524 	if (tty == -1)
    525 		return NULL;
    526 	if (tty != fd && dup2(tty, fd) == -1) {
    527 		close(tty);
    528 		return NULL;
    529 	}
    530 	close(tty);
    531 	return ui_termkey_new(fd);
    532 }
    533 
    534 void ui_terminal_suspend(Ui *tui) {
    535 	ui_term_backend_suspend(tui);
    536 	kill(0, SIGTSTP);
    537 }
    538 
    539 bool ui_getkey(Ui *tui, TermKeyKey *key) {
    540 	TermKeyResult ret = termkey_getkey(tui->termkey, key);
    541 
    542 	if (ret == TERMKEY_RES_EOF) {
    543 		termkey_destroy(tui->termkey);
    544 		errno = 0;
    545 		if (!(tui->termkey = ui_termkey_reopen(tui, STDIN_FILENO)))
    546 			ui_die_msg(tui, "Failed to re-open stdin as /dev/tty: %s\n", errno != 0 ? strerror(errno) : "");
    547 		return false;
    548 	}
    549 
    550 	if (ret == TERMKEY_RES_AGAIN) {
    551 		struct pollfd fd;
    552 		fd.fd = STDIN_FILENO;
    553 		fd.events = POLLIN;
    554 		if (poll(&fd, 1, termkey_get_waittime(tui->termkey)) == 0)
    555 			ret = termkey_getkey_force(tui->termkey, key);
    556 	}
    557 
    558 	return ret == TERMKEY_RES_KEY;
    559 }
    560 
    561 void ui_terminal_save(Ui *tui, bool fscr) {
    562 	ui_term_backend_save(tui, fscr);
    563 	termkey_stop(tui->termkey);
    564 }
    565 
    566 void ui_terminal_restore(Ui *tui) {
    567 	termkey_start(tui->termkey);
    568 	ui_term_backend_restore(tui);
    569 }
    570 
    571 bool ui_init(Ui *tui, Vis *vis) {
    572 	tui->vis = vis;
    573 
    574 	setlocale(LC_CTYPE, "");
    575 
    576 	char *term = getenv("TERM");
    577 	if (!term) {
    578 		term = "xterm";
    579 		setenv("TERM", term, 1);
    580 	}
    581 
    582 	errno = 0;
    583 	if (!(tui->termkey = ui_termkey_new(STDIN_FILENO))) {
    584 		/* work around libtermkey bug which fails if stdin is /dev/null */
    585 		if (errno == EBADF) {
    586 			errno = 0;
    587 			if (!(tui->termkey = ui_termkey_reopen(tui, STDIN_FILENO)) && errno == ENXIO)
    588 				tui->termkey = termkey_new_abstract(term, UI_TERMKEY_FLAGS);
    589 		}
    590 		if (!tui->termkey)
    591 			goto err;
    592 	}
    593 
    594 	if (!ui_term_backend_init(tui, term))
    595 		goto err;
    596 	ui_resize(tui);
    597 	return true;
    598 err:
    599 	ui_die_msg(tui, "Failed to start curses interface: %s\n", errno != 0 ? strerror(errno) : "");
    600 	return false;
    601 }
    602 
    603 bool ui_terminal_init(Ui *tui) {
    604 	size_t styles_size = UI_STYLE_MAX * sizeof(CellStyle);
    605 	CellStyle *styles = calloc(1, styles_size);
    606 	if (!styles)
    607 		return false;
    608 	if (!ui_backend_init(tui)) {
    609 		free(styles);
    610 		return false;
    611 	}
    612 	tui->styles_size = styles_size;
    613 	tui->styles = styles;
    614 	tui->doupdate = true;
    615 	return true;
    616 }
    617 
    618 void ui_terminal_free(Ui *tui) {
    619 	if (!tui)
    620 		return;
    621 	while (tui->windows)
    622 		ui_window_release(tui, tui->windows);
    623 	ui_term_backend_free(tui);
    624 	if (tui->termkey)
    625 		termkey_destroy(tui->termkey);
    626 	free(tui->cells);
    627 	free(tui->styles);
    628 }