view: Fix a @param name in a comment
[vis.git] / ui-terminal.c
blobbcf4f48adcdfdc00843a1cc5d37184e6514e2816
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 Selection *sel = view_selections_primary_get(view);
251 const Line *cursor_line = view_cursors_line_get(sel);
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,
275 (l->lineno == cursor_lineno) ? UI_STYLE_LINENUMBER_CURSOR : UI_STYLE_LINENUMBER);
276 prev_lineno = l->lineno;
278 debug("draw-window: [%d][%d] ... cells[%d][%d]\n", y, x+sidebar_width, y, view_width);
279 memcpy(&cells[y++][x+sidebar_width], l->cells, sizeof(Cell) * view_width);
283 static CellStyle ui_window_style_get(UiWin *w, enum UiStyle style) {
284 UiTermWin *win = (UiTermWin*)w;
285 UiTerm *tui = win->ui;
286 return tui->styles[win->id * UI_STYLE_MAX + style];
289 static void ui_window_status(UiWin *w, const char *status) {
290 UiTermWin *win = (UiTermWin*)w;
291 if (!(win->options & UI_OPTION_STATUSBAR))
292 return;
293 UiTerm *ui = win->ui;
294 enum UiStyle style = ui->selwin == win ? UI_STYLE_STATUS_FOCUSED : UI_STYLE_STATUS;
295 ui_draw_string(ui, win->x, win->y + win->height - 1, status, win, style);
298 static void ui_arrange(Ui *ui, enum UiLayout layout) {
299 debug("ui-arrange\n");
300 UiTerm *tui = (UiTerm*)ui;
301 tui->layout = layout;
302 Cell (*cells)[tui->width] = (void*)tui->cells;
303 int n = 0, m = !!tui->info[0], x = 0, y = 0;
304 for (UiTermWin *win = tui->windows; win; win = win->next) {
305 if (win->options & UI_OPTION_ONELINE)
306 m++;
307 else
308 n++;
310 int max_height = tui->height - m;
311 int width = (tui->width / MAX(1, n)) - 1;
312 int height = max_height / MAX(1, n);
313 for (UiTermWin *win = tui->windows; win; win = win->next) {
314 if (win->options & UI_OPTION_ONELINE)
315 continue;
316 n--;
317 if (layout == UI_LAYOUT_HORIZONTAL) {
318 int h = n ? height : max_height - y;
319 ui_window_resize(win, tui->width, h);
320 ui_window_move(win, x, y);
321 y += h;
322 } else {
323 int w = n ? width : tui->width - x;
324 ui_window_resize(win, w, max_height);
325 ui_window_move(win, x, y);
326 x += w;
327 if (n) {
328 for (int i = 0; i < max_height; i++) {
329 strcpy(cells[i][x].data,"│");
330 cells[i][x].style = tui->styles[UI_STYLE_SEPARATOR];
332 x++;
337 if (layout == UI_LAYOUT_VERTICAL)
338 y = max_height;
340 for (UiTermWin *win = tui->windows; win; win = win->next) {
341 if (!(win->options & UI_OPTION_ONELINE))
342 continue;
343 ui_window_resize(win, tui->width, 1);
344 ui_window_move(win, 0, y++);
348 static void ui_draw(Ui *ui) {
349 debug("ui-draw\n");
350 UiTerm *tui = (UiTerm*)ui;
351 ui_arrange(ui, tui->layout);
352 for (UiTermWin *win = tui->windows; win; win = win->next)
353 ui_window_draw((UiWin*)win);
354 if (tui->info[0])
355 ui_draw_string(tui, 0, tui->height-1, tui->info, NULL, UI_STYLE_INFO);
356 ui_term_backend_blit(tui);
359 static void ui_redraw(Ui *ui) {
360 UiTerm *tui = (UiTerm*)ui;
361 ui_term_backend_clear(tui);
362 for (UiTermWin *win = tui->windows; win; win = win->next)
363 view_invalidate(win->win->view);
366 static void ui_resize(Ui *ui) {
367 UiTerm *tui = (UiTerm*)ui;
368 struct winsize ws;
369 int width = 80, height = 24;
371 if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) != -1) {
372 width = ws.ws_col;
373 height = ws.ws_row;
376 width = MAX(width, 1);
377 width = MIN(width, MAX_WIDTH);
378 height = MAX(height, 1);
379 height = MIN(height, MAX_HEIGHT);
380 if (!ui_term_backend_resize(tui, width, height))
381 return;
383 size_t size = width*height*sizeof(Cell);
384 if (size > tui->cells_size) {
385 Cell *cells = realloc(tui->cells, size);
386 if (!cells)
387 return;
388 memset((char*)cells+tui->cells_size, 0, size - tui->cells_size);
389 tui->cells_size = size;
390 tui->cells = cells;
392 tui->width = width;
393 tui->height = height;
396 static void ui_window_free(UiWin *w) {
397 UiTermWin *win = (UiTermWin*)w;
398 if (!win)
399 return;
400 UiTerm *tui = win->ui;
401 if (win->prev)
402 win->prev->next = win->next;
403 if (win->next)
404 win->next->prev = win->prev;
405 if (tui->windows == win)
406 tui->windows = win->next;
407 if (tui->selwin == win)
408 tui->selwin = NULL;
409 win->next = win->prev = NULL;
410 tui->ids &= ~(1UL << win->id);
411 free(win);
414 static void ui_window_focus(UiWin *w) {
415 UiTermWin *new = (UiTermWin*)w;
416 UiTermWin *old = new->ui->selwin;
417 if (new->options & UI_OPTION_STATUSBAR)
418 new->ui->selwin = new;
419 if (old)
420 view_invalidate(old->win->view);
421 view_invalidate(new->win->view);
424 static void ui_window_options_set(UiWin *w, enum UiOption options) {
425 UiTermWin *win = (UiTermWin*)w;
426 win->options = options;
427 if (options & UI_OPTION_ONELINE) {
428 /* move the new window to the end of the list */
429 UiTerm *tui = win->ui;
430 UiTermWin *last = tui->windows;
431 while (last->next)
432 last = last->next;
433 if (last != win) {
434 if (win->prev)
435 win->prev->next = win->next;
436 if (win->next)
437 win->next->prev = win->prev;
438 if (tui->windows == win)
439 tui->windows = win->next;
440 last->next = win;
441 win->prev = last;
442 win->next = NULL;
445 ui_draw((Ui*)win->ui);
448 static enum UiOption ui_window_options_get(UiWin *win) {
449 return ((UiTermWin*)win)->options;
452 static int ui_window_width(UiWin *win) {
453 return ((UiTermWin*)win)->width;
456 static int ui_window_height(UiWin *win) {
457 return ((UiTermWin*)win)->height;
460 static void ui_window_swap(UiWin *aw, UiWin *bw) {
461 UiTermWin *a = (UiTermWin*)aw;
462 UiTermWin *b = (UiTermWin*)bw;
463 if (a == b || !a || !b)
464 return;
465 UiTerm *tui = a->ui;
466 UiTermWin *tmp = a->next;
467 a->next = b->next;
468 b->next = tmp;
469 if (a->next)
470 a->next->prev = a;
471 if (b->next)
472 b->next->prev = b;
473 tmp = a->prev;
474 a->prev = b->prev;
475 b->prev = tmp;
476 if (a->prev)
477 a->prev->next = a;
478 if (b->prev)
479 b->prev->next = b;
480 if (tui->windows == a)
481 tui->windows = b;
482 else if (tui->windows == b)
483 tui->windows = a;
484 if (tui->selwin == a)
485 ui_window_focus(bw);
486 else if (tui->selwin == b)
487 ui_window_focus(aw);
490 static UiWin *ui_window_new(Ui *ui, Win *w, enum UiOption options) {
491 UiTerm *tui = (UiTerm*)ui;
492 /* get rightmost zero bit, i.e. highest available id */
493 size_t bit = ~tui->ids & (tui->ids + 1);
494 size_t id = 0;
495 for (size_t tmp = bit; tmp >>= 1; id++);
496 if (id >= sizeof(size_t) * 8)
497 return NULL;
498 size_t styles_size = (id + 1) * UI_STYLE_MAX * sizeof(CellStyle);
499 if (styles_size > tui->styles_size) {
500 CellStyle *styles = realloc(tui->styles, styles_size);
501 if (!styles)
502 return NULL;
503 tui->styles = styles;
504 tui->styles_size = styles_size;
506 UiTermWin *win = calloc(1, sizeof(UiTermWin));
507 if (!win)
508 return NULL;
510 win->uiwin = (UiWin) {
511 .style_get = ui_window_style_get,
512 .status = ui_window_status,
513 .options_set = ui_window_options_set,
514 .options_get = ui_window_options_get,
515 .style_define = ui_style_define,
516 .window_width = ui_window_width,
517 .window_height = ui_window_height,
520 tui->ids |= bit;
521 win->id = id;
522 win->ui = tui;
523 win->win = w;
525 CellStyle *styles = &tui->styles[win->id * UI_STYLE_MAX];
526 for (int i = 0; i < UI_STYLE_MAX; i++) {
527 styles[i] = (CellStyle) {
528 .fg = CELL_COLOR_DEFAULT,
529 .bg = CELL_COLOR_DEFAULT,
530 .attr = CELL_ATTR_NORMAL,
534 styles[UI_STYLE_CURSOR].attr |= CELL_ATTR_REVERSE;
535 styles[UI_STYLE_CURSOR_PRIMARY].attr |= CELL_ATTR_REVERSE|CELL_ATTR_BLINK;
536 styles[UI_STYLE_SELECTION].attr |= CELL_ATTR_REVERSE;
537 styles[UI_STYLE_COLOR_COLUMN].attr |= CELL_ATTR_REVERSE;
538 styles[UI_STYLE_STATUS].attr |= CELL_ATTR_REVERSE;
539 styles[UI_STYLE_STATUS_FOCUSED].attr |= CELL_ATTR_REVERSE|CELL_ATTR_BOLD;
540 styles[UI_STYLE_INFO].attr |= CELL_ATTR_BOLD;
541 view_ui(w->view, &win->uiwin);
543 if (tui->windows)
544 tui->windows->prev = win;
545 win->next = tui->windows;
546 tui->windows = win;
548 if (text_size(w->file->text) > UI_LARGE_FILE_SIZE) {
549 options |= UI_OPTION_LARGE_FILE;
550 options &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
553 ui_window_options_set((UiWin*)win, options);
555 return &win->uiwin;
558 static void ui_info(Ui *ui, const char *msg, va_list ap) {
559 UiTerm *tui = (UiTerm*)ui;
560 ui_draw_line(tui, 0, tui->height-1, ' ', UI_STYLE_INFO);
561 vsnprintf(tui->info, sizeof(tui->info), msg, ap);
564 static void ui_info_hide(Ui *ui) {
565 UiTerm *tui = (UiTerm*)ui;
566 if (tui->info[0])
567 tui->info[0] = '\0';
570 static TermKey *ui_termkey_new(int fd) {
571 TermKey *termkey = termkey_new(fd, UI_TERMKEY_FLAGS);
572 if (termkey)
573 termkey_set_canonflags(termkey, TERMKEY_CANON_DELBS);
574 return termkey;
577 static TermKey *ui_termkey_reopen(Ui *ui, int fd) {
578 int tty = open("/dev/tty", O_RDWR);
579 if (tty == -1)
580 return NULL;
581 if (tty != fd && dup2(tty, fd) == -1) {
582 close(tty);
583 return NULL;
585 close(tty);
586 return ui_termkey_new(fd);
589 static TermKey *ui_termkey_get(Ui *ui) {
590 UiTerm *tui = (UiTerm*)ui;
591 return tui->termkey;
594 static void ui_suspend(Ui *ui) {
595 UiTerm *tui = (UiTerm*)ui;
596 ui_term_backend_suspend(tui);
597 kill(0, SIGTSTP);
600 static void ui_resume(Ui *ui) {
601 UiTerm *tui = (UiTerm*)ui;
602 ui_term_backend_resume(tui);
605 static bool ui_getkey(Ui *ui, TermKeyKey *key) {
606 UiTerm *tui = (UiTerm*)ui;
607 TermKeyResult ret = termkey_getkey(tui->termkey, key);
609 if (ret == TERMKEY_RES_EOF) {
610 termkey_destroy(tui->termkey);
611 errno = 0;
612 if (!(tui->termkey = ui_termkey_reopen(ui, STDIN_FILENO)))
613 ui_die_msg(ui, "Failed to re-open stdin as /dev/tty: %s\n", errno != 0 ? strerror(errno) : "");
614 return false;
617 if (ret == TERMKEY_RES_AGAIN) {
618 struct pollfd fd;
619 fd.fd = STDIN_FILENO;
620 fd.events = POLLIN;
621 if (poll(&fd, 1, termkey_get_waittime(tui->termkey)) == 0)
622 ret = termkey_getkey_force(tui->termkey, key);
625 return ret == TERMKEY_RES_KEY;
628 static void ui_terminal_save(Ui *ui) {
629 UiTerm *tui = (UiTerm*)ui;
630 ui_term_backend_save(tui);
631 termkey_stop(tui->termkey);
634 static void ui_terminal_restore(Ui *ui) {
635 UiTerm *tui = (UiTerm*)ui;
636 termkey_start(tui->termkey);
637 ui_term_backend_restore(tui);
640 static bool ui_init(Ui *ui, Vis *vis) {
641 UiTerm *tui = (UiTerm*)ui;
642 tui->vis = vis;
644 setlocale(LC_CTYPE, "");
646 char *term = getenv("TERM");
647 if (!term) {
648 term = "xterm";
649 setenv("TERM", term, 1);
652 errno = 0;
653 if (!(tui->termkey = ui_termkey_new(STDIN_FILENO))) {
654 /* work around libtermkey bug which fails if stdin is /dev/null */
655 if (errno == EBADF) {
656 errno = 0;
657 if (!(tui->termkey = ui_termkey_reopen(ui, STDIN_FILENO)) && errno == ENXIO)
658 tui->termkey = termkey_new_abstract(term, UI_TERMKEY_FLAGS);
660 if (!tui->termkey)
661 goto err;
664 if (!ui_term_backend_init(tui, term))
665 goto err;
666 ui_resize(ui);
667 return true;
668 err:
669 ui_die_msg(ui, "Failed to start curses interface: %s\n", errno != 0 ? strerror(errno) : "");
670 return false;
673 Ui *ui_term_new(void) {
674 size_t styles_size = UI_STYLE_MAX * sizeof(CellStyle);
675 CellStyle *styles = calloc(1, styles_size);
676 if (!styles)
677 return NULL;
678 UiTerm *tui = ui_term_backend_new();
679 if (!tui) {
680 free(styles);
681 return NULL;
683 tui->styles_size = styles_size;
684 tui->styles = styles;
685 Ui *ui = (Ui*)tui;
686 *ui = (Ui) {
687 .init = ui_init,
688 .free = ui_term_free,
689 .termkey_get = ui_termkey_get,
690 .suspend = ui_suspend,
691 .resume = ui_resume,
692 .resize = ui_resize,
693 .window_new = ui_window_new,
694 .window_free = ui_window_free,
695 .window_focus = ui_window_focus,
696 .window_swap = ui_window_swap,
697 .draw = ui_draw,
698 .redraw = ui_redraw,
699 .arrange = ui_arrange,
700 .die = ui_die,
701 .info = ui_info,
702 .info_hide = ui_info_hide,
703 .getkey = ui_getkey,
704 .terminal_save = ui_terminal_save,
705 .terminal_restore = ui_terminal_restore,
706 .colors = ui_term_backend_colors,
709 return ui;
712 void ui_term_free(Ui *ui) {
713 UiTerm *tui = (UiTerm*)ui;
714 if (!tui)
715 return;
716 while (tui->windows)
717 ui_window_free((UiWin*)tui->windows);
718 ui_term_backend_free(tui);
719 if (tui->termkey)
720 termkey_destroy(tui->termkey);
721 free(tui->cells);
722 free(tui->styles);
723 free(tui);