view: rename view_cursors_selection_save
[vis.git] / ui-terminal.c
blob6ba31645ed898ca16dc0beaa2ccde00ceb632e4e
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>
16 #include "ui-terminal.h"
17 #include "vis.h"
18 #include "vis-core.h"
19 #include "text.h"
20 #include "util.h"
21 #include "text-util.h"
23 #ifndef DEBUG_UI
24 #define DEBUG_UI 0
25 #endif
27 #if DEBUG_UI
28 #define debug(...) do { printf(__VA_ARGS__); fflush(stdout); } while (0)
29 #else
30 #define debug(...) do { } while (0)
31 #endif
33 #define MAX_WIDTH 1024
34 #define MAX_HEIGHT 1024
35 typedef struct UiTermWin UiTermWin;
37 typedef struct {
38 Ui ui; /* generic ui interface, has to be the first struct member */
39 Vis *vis; /* editor instance to which this ui belongs */
40 UiTermWin *windows; /* all windows managed by this ui */
41 UiTermWin *selwin; /* the currently selected layout */
42 char info[MAX_WIDTH]; /* info message displayed at the bottom of the screen */
43 int width, height; /* terminal dimensions available for all windows */
44 enum UiLayout layout; /* whether windows are displayed horizontally or vertically */
45 TermKey *termkey; /* libtermkey instance to handle keyboard input (stdin or /dev/tty) */
46 size_t ids; /* bit mask of in use window ids */
47 size_t styles_size; /* #bytes allocated for styles array */
48 CellStyle *styles; /* each window has UI_STYLE_MAX different style definitions */
49 size_t cells_size; /* #bytes allocated for 2D grid (grows only) */
50 Cell *cells; /* 2D grid of cells, at least as large as current terminal size */
51 } UiTerm;
53 struct UiTermWin {
54 UiWin uiwin; /* generic interface, has to be the first struct member */
55 UiTerm *ui; /* ui which manages this window */
56 Win *win; /* editor window being displayed */
57 int id; /* unique identifier for this window */
58 int width, height; /* window dimension including status bar */
59 int x, y; /* window position */
60 int sidebar_width; /* width of the sidebar showing line numbers etc. */
61 UiTermWin *next, *prev; /* pointers to neighbouring windows */
62 enum UiOption options; /* display settings for this window */
65 #if CONFIG_CURSES
66 #include "ui-terminal-curses.c"
67 #else
68 #include "ui-terminal-vt100.c"
69 #endif
71 __attribute__((noreturn)) static void ui_die(Ui *ui, const char *msg, va_list ap) {
72 UiTerm *tui = (UiTerm*)ui;
73 ui_term_backend_free(tui);
74 if (tui->termkey)
75 termkey_stop(tui->termkey);
76 vfprintf(stderr, msg, ap);
77 exit(EXIT_FAILURE);
80 __attribute__((noreturn)) static void ui_die_msg(Ui *ui, const char *msg, ...) {
81 va_list ap;
82 va_start(ap, msg);
83 ui_die(ui, msg, ap);
84 va_end(ap);
87 static void ui_window_resize(UiTermWin *win, int width, int height) {
88 debug("ui-win-resize[%s]: %dx%d\n", win->win->file->name ? win->win->file->name : "noname", width, height);
89 bool status = win->options & UI_OPTION_STATUSBAR;
90 win->width = width;
91 win->height = height;
92 view_resize(win->win->view, width - win->sidebar_width, status ? height - 1 : height);
95 static void ui_window_move(UiTermWin *win, int x, int y) {
96 debug("ui-win-move[%s]: (%d, %d)\n", win->win->file->name ? win->win->file->name : "noname", x, y);
97 win->x = x;
98 win->y = y;
101 static bool color_fromstring(UiTerm *ui, CellColor *color, const char *s)
103 if (!s)
104 return false;
105 if (*s == '#' && strlen(s) == 7) {
106 const char *cp;
107 unsigned char r, g, b;
108 for (cp = s + 1; isxdigit((unsigned char)*cp); cp++);
109 if (*cp != '\0')
110 return false;
111 int n = sscanf(s + 1, "%2hhx%2hhx%2hhx", &r, &g, &b);
112 if (n != 3)
113 return false;
114 *color = color_rgb(ui, r, g, b);
115 return true;
116 } else if ('0' <= *s && *s <= '9') {
117 int index = atoi(s);
118 if (index <= 0 || index > 255)
119 return false;
120 *color = color_terminal(ui, index);
121 return true;
124 static const struct {
125 const char *name;
126 CellColor color;
127 } color_names[] = {
128 { "black", CELL_COLOR_BLACK },
129 { "red", CELL_COLOR_RED },
130 { "green", CELL_COLOR_GREEN },
131 { "yellow", CELL_COLOR_YELLOW },
132 { "blue", CELL_COLOR_BLUE },
133 { "magenta", CELL_COLOR_MAGENTA },
134 { "cyan", CELL_COLOR_CYAN },
135 { "white", CELL_COLOR_WHITE },
136 { "default", CELL_COLOR_DEFAULT },
139 for (size_t i = 0; i < LENGTH(color_names); i++) {
140 if (strcasecmp(color_names[i].name, s) == 0) {
141 *color = color_names[i].color;
142 return true;
146 return false;
149 static bool ui_style_define(UiWin *w, int id, const char *style) {
150 UiTermWin *win = (UiTermWin*)w;
151 UiTerm *tui = win->ui;
152 if (id >= UI_STYLE_MAX)
153 return false;
154 if (!style)
155 return true;
156 CellStyle cell_style = tui->styles[win->id * UI_STYLE_MAX + UI_STYLE_DEFAULT];
157 char *style_copy = strdup(style), *option = style_copy;
158 while (option) {
159 while (*option == ' ')
160 option++;
161 char *next = strchr(option, ',');
162 if (next)
163 *next++ = '\0';
164 char *value = strchr(option, ':');
165 if (value)
166 for (*value++ = '\0'; *value == ' '; value++);
167 if (!strcasecmp(option, "reverse")) {
168 cell_style.attr |= CELL_ATTR_REVERSE;
169 } else if (!strcasecmp(option, "bold")) {
170 cell_style.attr |= CELL_ATTR_BOLD;
171 } else if (!strcasecmp(option, "notbold")) {
172 cell_style.attr &= ~CELL_ATTR_BOLD;
173 } else if (!strcasecmp(option, "italics")) {
174 cell_style.attr |= CELL_ATTR_ITALIC;
175 } else if (!strcasecmp(option, "notitalics")) {
176 cell_style.attr &= ~CELL_ATTR_ITALIC;
177 } else if (!strcasecmp(option, "underlined")) {
178 cell_style.attr |= CELL_ATTR_UNDERLINE;
179 } else if (!strcasecmp(option, "notunderlined")) {
180 cell_style.attr &= ~CELL_ATTR_UNDERLINE;
181 } else if (!strcasecmp(option, "blink")) {
182 cell_style.attr |= CELL_ATTR_BLINK;
183 } else if (!strcasecmp(option, "notblink")) {
184 cell_style.attr &= ~CELL_ATTR_BLINK;
185 } else if (!strcasecmp(option, "fore")) {
186 color_fromstring(win->ui, &cell_style.fg, value);
187 } else if (!strcasecmp(option, "back")) {
188 color_fromstring(win->ui, &cell_style.bg, value);
190 option = next;
192 tui->styles[win->id * UI_STYLE_MAX + id] = cell_style;
193 free(style_copy);
194 return true;
197 static void ui_draw_line(UiTerm *tui, int x, int y, char c, enum UiStyle style_id) {
198 if (x < 0 || x >= tui->width || y < 0 || y >= tui->height)
199 return;
200 CellStyle style = tui->styles[style_id];
201 Cell (*cells)[tui->width] = (void*)tui->cells;
202 while (x < tui->width) {
203 cells[y][x].data[0] = c;
204 cells[y][x].data[1] = '\0';
205 cells[y][x].style = style;
206 x++;
210 static void ui_draw_string(UiTerm *tui, int x, int y, const char *str, UiTermWin *win, enum UiStyle style_id) {
211 debug("draw-string: [%d][%d]\n", y, x);
212 if (x < 0 || x >= tui->width || y < 0 || y >= tui->height)
213 return;
214 CellStyle style = tui->styles[(win ? win->id : 0)*UI_STYLE_MAX + style_id];
215 // FIXME: does not handle double width characters etc, share code with view.c?
216 Cell (*cells)[tui->width] = (void*)tui->cells;
217 const size_t cell_size = sizeof(cells[0][0].data)-1;
218 for (const char *next = str; *str && x < tui->width; str = next) {
219 do next++; while (!ISUTF8(*next));
220 size_t len = next - str;
221 if (!len)
222 break;
223 len = MIN(len, cell_size);
224 strncpy(cells[y][x].data, str, len);
225 cells[y][x].data[len] = '\0';
226 cells[y][x].style = style;
227 x++;
231 static void ui_window_draw(UiWin *w) {
232 UiTermWin *win = (UiTermWin*)w;
233 UiTerm *ui = win->ui;
234 View *view = win->win->view;
235 Cell (*cells)[ui->width] = (void*)ui->cells;
236 int width = win->width, height = win->height;
237 const Line *line = view_lines_first(view);
238 bool status = win->options & UI_OPTION_STATUSBAR;
239 bool nu = win->options & UI_OPTION_LINE_NUMBERS_ABSOLUTE;
240 bool rnu = win->options & UI_OPTION_LINE_NUMBERS_RELATIVE;
241 bool sidebar = nu || rnu;
242 int sidebar_width = sidebar ? snprintf(NULL, 0, "%zd ", line->lineno + height - 2) : 0;
243 if (sidebar_width != win->sidebar_width) {
244 view_resize(view, width - sidebar_width, status ? height - 1 : height);
245 win->sidebar_width = sidebar_width;
247 vis_window_draw(win->win);
248 line = view_lines_first(view);
249 size_t prev_lineno = 0;
250 Cursor *cursor = view_selections_primary_get(view);
251 const Line *cursor_line = view_cursors_line_get(cursor);
252 size_t cursor_lineno = cursor_line->lineno;
253 char buf[sidebar_width+1];
254 int x = win->x, y = win->y;
255 int view_width = view_width_get(view);
256 if (x + sidebar_width + view_width > ui->width)
257 view_width = ui->width - x - sidebar_width;
258 for (const Line *l = line; l; l = l->next) {
259 if (sidebar) {
260 if (!l->lineno || !l->len || l->lineno == prev_lineno) {
261 memset(buf, ' ', sizeof(buf));
262 buf[sidebar_width] = '\0';
263 } else {
264 size_t number = l->lineno;
265 if (rnu) {
266 number = (win->options & UI_OPTION_LARGE_FILE) ? 0 : l->lineno;
267 if (l->lineno > cursor_lineno)
268 number = l->lineno - cursor_lineno;
269 else if (l->lineno < cursor_lineno)
270 number = cursor_lineno - l->lineno;
272 snprintf(buf, sizeof buf, "%*zu ", sidebar_width-1, number);
274 ui_draw_string(ui, x, y, buf, win, UI_STYLE_LINENUMBER);
275 prev_lineno = l->lineno;
277 debug("draw-window: [%d][%d] ... cells[%d][%d]\n", y, x+sidebar_width, y, view_width);
278 memcpy(&cells[y++][x+sidebar_width], l->cells, sizeof(Cell) * view_width);
282 static CellStyle ui_window_style_get(UiWin *w, enum UiStyle style) {
283 UiTermWin *win = (UiTermWin*)w;
284 UiTerm *tui = win->ui;
285 return tui->styles[win->id * UI_STYLE_MAX + style];
288 static void ui_window_status(UiWin *w, const char *status) {
289 UiTermWin *win = (UiTermWin*)w;
290 if (!(win->options & UI_OPTION_STATUSBAR))
291 return;
292 UiTerm *ui = win->ui;
293 enum UiStyle style = ui->selwin == win ? UI_STYLE_STATUS_FOCUSED : UI_STYLE_STATUS;
294 ui_draw_string(ui, win->x, win->y + win->height - 1, status, win, style);
297 static void ui_arrange(Ui *ui, enum UiLayout layout) {
298 debug("ui-arrange\n");
299 UiTerm *tui = (UiTerm*)ui;
300 tui->layout = layout;
301 Cell (*cells)[tui->width] = (void*)tui->cells;
302 int n = 0, m = !!tui->info[0], x = 0, y = 0;
303 for (UiTermWin *win = tui->windows; win; win = win->next) {
304 if (win->options & UI_OPTION_ONELINE)
305 m++;
306 else
307 n++;
309 int max_height = tui->height - m;
310 int width = (tui->width / MAX(1, n)) - 1;
311 int height = max_height / MAX(1, n);
312 for (UiTermWin *win = tui->windows; win; win = win->next) {
313 if (win->options & UI_OPTION_ONELINE)
314 continue;
315 n--;
316 if (layout == UI_LAYOUT_HORIZONTAL) {
317 int h = n ? height : max_height - y;
318 ui_window_resize(win, tui->width, h);
319 ui_window_move(win, x, y);
320 y += h;
321 } else {
322 int w = n ? width : tui->width - x;
323 ui_window_resize(win, w, max_height);
324 ui_window_move(win, x, y);
325 x += w;
326 if (n) {
327 for (int i = 0; i < max_height; i++) {
328 strcpy(cells[i][x].data,"│");
329 cells[i][x].style = tui->styles[UI_STYLE_SEPARATOR];
331 x++;
336 if (layout == UI_LAYOUT_VERTICAL)
337 y = max_height;
339 for (UiTermWin *win = tui->windows; win; win = win->next) {
340 if (!(win->options & UI_OPTION_ONELINE))
341 continue;
342 ui_window_resize(win, tui->width, 1);
343 ui_window_move(win, 0, y++);
347 static void ui_draw(Ui *ui) {
348 debug("ui-draw\n");
349 UiTerm *tui = (UiTerm*)ui;
350 ui_arrange(ui, tui->layout);
351 for (UiTermWin *win = tui->windows; win; win = win->next)
352 ui_window_draw((UiWin*)win);
353 if (tui->info[0])
354 ui_draw_string(tui, 0, tui->height-1, tui->info, NULL, UI_STYLE_INFO);
355 ui_term_backend_blit(tui);
358 static void ui_redraw(Ui *ui) {
359 UiTerm *tui = (UiTerm*)ui;
360 ui_term_backend_clear(tui);
361 for (UiTermWin *win = tui->windows; win; win = win->next)
362 view_invalidate(win->win->view);
365 static void ui_resize(Ui *ui) {
366 UiTerm *tui = (UiTerm*)ui;
367 struct winsize ws;
368 int width = 80, height = 24;
370 if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) != -1) {
371 width = ws.ws_col;
372 height = ws.ws_row;
375 width = MAX(width, 1);
376 width = MIN(width, MAX_WIDTH);
377 height = MAX(height, 1);
378 height = MIN(height, MAX_HEIGHT);
379 if (!ui_term_backend_resize(tui, width, height))
380 return;
382 size_t size = width*height*sizeof(Cell);
383 if (size > tui->cells_size) {
384 Cell *cells = realloc(tui->cells, size);
385 if (!cells)
386 return;
387 memset((char*)cells+tui->cells_size, 0, size - tui->cells_size);
388 tui->cells_size = size;
389 tui->cells = cells;
391 tui->width = width;
392 tui->height = height;
395 static void ui_window_free(UiWin *w) {
396 UiTermWin *win = (UiTermWin*)w;
397 if (!win)
398 return;
399 UiTerm *tui = win->ui;
400 if (win->prev)
401 win->prev->next = win->next;
402 if (win->next)
403 win->next->prev = win->prev;
404 if (tui->windows == win)
405 tui->windows = win->next;
406 if (tui->selwin == win)
407 tui->selwin = NULL;
408 win->next = win->prev = NULL;
409 tui->ids &= ~(1UL << win->id);
410 free(win);
413 static void ui_window_focus(UiWin *w) {
414 UiTermWin *new = (UiTermWin*)w;
415 UiTermWin *old = new->ui->selwin;
416 if (new->options & UI_OPTION_STATUSBAR)
417 new->ui->selwin = new;
418 if (old)
419 view_invalidate(old->win->view);
420 view_invalidate(new->win->view);
423 static void ui_window_options_set(UiWin *w, enum UiOption options) {
424 UiTermWin *win = (UiTermWin*)w;
425 win->options = options;
426 if (options & UI_OPTION_ONELINE) {
427 /* move the new window to the end of the list */
428 UiTerm *tui = win->ui;
429 UiTermWin *last = tui->windows;
430 while (last->next)
431 last = last->next;
432 if (last != win) {
433 if (win->prev)
434 win->prev->next = win->next;
435 if (win->next)
436 win->next->prev = win->prev;
437 if (tui->windows == win)
438 tui->windows = win->next;
439 last->next = win;
440 win->prev = last;
441 win->next = NULL;
444 ui_draw((Ui*)win->ui);
447 static enum UiOption ui_window_options_get(UiWin *win) {
448 return ((UiTermWin*)win)->options;
451 static int ui_window_width(UiWin *win) {
452 return ((UiTermWin*)win)->width;
455 static int ui_window_height(UiWin *win) {
456 return ((UiTermWin*)win)->height;
459 static void ui_window_swap(UiWin *aw, UiWin *bw) {
460 UiTermWin *a = (UiTermWin*)aw;
461 UiTermWin *b = (UiTermWin*)bw;
462 if (a == b || !a || !b)
463 return;
464 UiTerm *tui = a->ui;
465 UiTermWin *tmp = a->next;
466 a->next = b->next;
467 b->next = tmp;
468 if (a->next)
469 a->next->prev = a;
470 if (b->next)
471 b->next->prev = b;
472 tmp = a->prev;
473 a->prev = b->prev;
474 b->prev = tmp;
475 if (a->prev)
476 a->prev->next = a;
477 if (b->prev)
478 b->prev->next = b;
479 if (tui->windows == a)
480 tui->windows = b;
481 else if (tui->windows == b)
482 tui->windows = a;
483 if (tui->selwin == a)
484 ui_window_focus(bw);
485 else if (tui->selwin == b)
486 ui_window_focus(aw);
489 static UiWin *ui_window_new(Ui *ui, Win *w, enum UiOption options) {
490 UiTerm *tui = (UiTerm*)ui;
491 /* get rightmost zero bit, i.e. highest available id */
492 size_t bit = ~tui->ids & (tui->ids + 1);
493 size_t id = 0;
494 for (size_t tmp = bit; tmp >>= 1; id++);
495 if (id >= sizeof(size_t) * 8)
496 return NULL;
497 size_t styles_size = (id + 1) * UI_STYLE_MAX * sizeof(CellStyle);
498 if (styles_size > tui->styles_size) {
499 CellStyle *styles = realloc(tui->styles, styles_size);
500 if (!styles)
501 return NULL;
502 tui->styles = styles;
503 tui->styles_size = styles_size;
505 UiTermWin *win = calloc(1, sizeof(UiTermWin));
506 if (!win)
507 return NULL;
509 win->uiwin = (UiWin) {
510 .style_get = ui_window_style_get,
511 .status = ui_window_status,
512 .options_set = ui_window_options_set,
513 .options_get = ui_window_options_get,
514 .style_define = ui_style_define,
515 .window_width = ui_window_width,
516 .window_height = ui_window_height,
519 tui->ids |= bit;
520 win->id = id;
521 win->ui = tui;
522 win->win = w;
524 CellStyle *styles = &tui->styles[win->id * UI_STYLE_MAX];
525 for (int i = 0; i < UI_STYLE_MAX; i++) {
526 styles[i] = (CellStyle) {
527 .fg = CELL_COLOR_DEFAULT,
528 .bg = CELL_COLOR_DEFAULT,
529 .attr = CELL_ATTR_NORMAL,
533 styles[UI_STYLE_CURSOR].attr |= CELL_ATTR_REVERSE;
534 styles[UI_STYLE_CURSOR_PRIMARY].attr |= CELL_ATTR_REVERSE|CELL_ATTR_BLINK;
535 styles[UI_STYLE_SELECTION].attr |= CELL_ATTR_REVERSE;
536 styles[UI_STYLE_COLOR_COLUMN].attr |= CELL_ATTR_REVERSE;
537 styles[UI_STYLE_STATUS].attr |= CELL_ATTR_REVERSE;
538 styles[UI_STYLE_STATUS_FOCUSED].attr |= CELL_ATTR_REVERSE|CELL_ATTR_BOLD;
539 styles[UI_STYLE_INFO].attr |= CELL_ATTR_BOLD;
540 view_ui(w->view, &win->uiwin);
542 if (tui->windows)
543 tui->windows->prev = win;
544 win->next = tui->windows;
545 tui->windows = win;
547 if (text_size(w->file->text) > UI_LARGE_FILE_SIZE) {
548 options |= UI_OPTION_LARGE_FILE;
549 options &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
552 ui_window_options_set((UiWin*)win, options);
554 return &win->uiwin;
557 static void ui_info(Ui *ui, const char *msg, va_list ap) {
558 UiTerm *tui = (UiTerm*)ui;
559 ui_draw_line(tui, 0, tui->height-1, ' ', UI_STYLE_INFO);
560 vsnprintf(tui->info, sizeof(tui->info), msg, ap);
563 static void ui_info_hide(Ui *ui) {
564 UiTerm *tui = (UiTerm*)ui;
565 if (tui->info[0])
566 tui->info[0] = '\0';
569 static TermKey *ui_termkey_new(int fd) {
570 TermKey *termkey = termkey_new(fd, UI_TERMKEY_FLAGS);
571 if (termkey)
572 termkey_set_canonflags(termkey, TERMKEY_CANON_DELBS);
573 return termkey;
576 static TermKey *ui_termkey_reopen(Ui *ui, int fd) {
577 int tty = open("/dev/tty", O_RDWR);
578 if (tty == -1)
579 return NULL;
580 if (tty != fd && dup2(tty, fd) == -1) {
581 close(tty);
582 return NULL;
584 close(tty);
585 return ui_termkey_new(fd);
588 static TermKey *ui_termkey_get(Ui *ui) {
589 UiTerm *tui = (UiTerm*)ui;
590 return tui->termkey;
593 static void ui_suspend(Ui *ui) {
594 UiTerm *tui = (UiTerm*)ui;
595 ui_term_backend_suspend(tui);
596 kill(0, SIGTSTP);
599 static void ui_resume(Ui *ui) {
600 UiTerm *tui = (UiTerm*)ui;
601 ui_term_backend_resume(tui);
604 static bool ui_getkey(Ui *ui, TermKeyKey *key) {
605 UiTerm *tui = (UiTerm*)ui;
606 TermKeyResult ret = termkey_getkey(tui->termkey, key);
608 if (ret == TERMKEY_RES_EOF) {
609 termkey_destroy(tui->termkey);
610 errno = 0;
611 if (!(tui->termkey = ui_termkey_reopen(ui, STDIN_FILENO)))
612 ui_die_msg(ui, "Failed to re-open stdin as /dev/tty: %s\n", errno != 0 ? strerror(errno) : "");
613 return false;
616 if (ret == TERMKEY_RES_AGAIN) {
617 struct pollfd fd;
618 fd.fd = STDIN_FILENO;
619 fd.events = POLLIN;
620 if (poll(&fd, 1, termkey_get_waittime(tui->termkey)) == 0)
621 ret = termkey_getkey_force(tui->termkey, key);
624 return ret == TERMKEY_RES_KEY;
627 static void ui_terminal_save(Ui *ui) {
628 UiTerm *tui = (UiTerm*)ui;
629 ui_term_backend_save(tui);
630 termkey_stop(tui->termkey);
633 static void ui_terminal_restore(Ui *ui) {
634 UiTerm *tui = (UiTerm*)ui;
635 termkey_start(tui->termkey);
636 ui_term_backend_restore(tui);
639 static bool ui_init(Ui *ui, Vis *vis) {
640 UiTerm *tui = (UiTerm*)ui;
641 tui->vis = vis;
643 setlocale(LC_CTYPE, "");
645 char *term = getenv("TERM");
646 if (!term)
647 term = "xterm";
649 errno = 0;
650 if (!(tui->termkey = ui_termkey_new(STDIN_FILENO))) {
651 /* work around libtermkey bug which fails if stdin is /dev/null */
652 if (errno == EBADF) {
653 errno = 0;
654 if (!(tui->termkey = ui_termkey_reopen(ui, STDIN_FILENO)) && errno == ENXIO)
655 tui->termkey = termkey_new_abstract(term, UI_TERMKEY_FLAGS);
657 if (!tui->termkey)
658 goto err;
661 if (!ui_term_backend_init(tui, term))
662 goto err;
663 ui_resize(ui);
664 return true;
665 err:
666 ui_die_msg(ui, "Failed to start curses interface: %s\n", errno != 0 ? strerror(errno) : "");
667 return false;
670 Ui *ui_term_new(void) {
671 size_t styles_size = UI_STYLE_MAX * sizeof(CellStyle);
672 CellStyle *styles = calloc(1, styles_size);
673 if (!styles)
674 return NULL;
675 UiTerm *tui = ui_term_backend_new();
676 if (!tui) {
677 free(styles);
678 return NULL;
680 tui->styles_size = styles_size;
681 tui->styles = styles;
682 Ui *ui = (Ui*)tui;
683 *ui = (Ui) {
684 .init = ui_init,
685 .free = ui_term_free,
686 .termkey_get = ui_termkey_get,
687 .suspend = ui_suspend,
688 .resume = ui_resume,
689 .resize = ui_resize,
690 .window_new = ui_window_new,
691 .window_free = ui_window_free,
692 .window_focus = ui_window_focus,
693 .window_swap = ui_window_swap,
694 .draw = ui_draw,
695 .redraw = ui_redraw,
696 .arrange = ui_arrange,
697 .die = ui_die,
698 .info = ui_info,
699 .info_hide = ui_info_hide,
700 .getkey = ui_getkey,
701 .terminal_save = ui_terminal_save,
702 .terminal_restore = ui_terminal_restore,
703 .colors = ui_term_backend_colors,
706 return ui;
709 void ui_term_free(Ui *ui) {
710 UiTerm *tui = (UiTerm*)ui;
711 if (!tui)
712 return;
713 while (tui->windows)
714 ui_window_free((UiWin*)tui->windows);
715 ui_term_backend_free(tui);
716 if (tui->termkey)
717 termkey_destroy(tui->termkey);
718 free(tui->cells);
719 free(tui->styles);
720 free(tui);